summaryrefslogtreecommitdiff
path: root/core/io/http_client.cpp
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2014-02-09 22:10:30 -0300
committerJuan Linietsky <reduzio@gmail.com>2014-02-09 22:10:30 -0300
commit0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac (patch)
tree276c4d099e178eb67fbd14f61d77b05e3808e9e3 /core/io/http_client.cpp
parent0e49da1687bc8192ed210947da52c9e5c5f301bb (diff)
GODOT IS OPEN SOURCE
Diffstat (limited to 'core/io/http_client.cpp')
-rw-r--r--core/io/http_client.cpp641
1 files changed, 641 insertions, 0 deletions
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
new file mode 100644
index 0000000000..33c5f23f6d
--- /dev/null
+++ b/core/io/http_client.cpp
@@ -0,0 +1,641 @@
+/*************************************************************************/
+/* http_client.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "http_client.h"
+
+
+Error HTTPClient::connect_url(const String& p_url) {
+
+ return OK;
+}
+
+Error HTTPClient::connect(const String &p_host,int p_port){
+
+ close();
+ conn_port=p_port;
+ conn_host=p_host;
+
+ if (conn_host.begins_with("http://")) {
+
+ conn_host=conn_host.replace_first("http://","");
+ } else if (conn_host.begins_with("https://")) {
+ //use https
+ conn_host=conn_host.replace_first("https://","");
+ }
+
+
+ connection=tcp_connection;
+ if (conn_host.is_valid_ip_address()) {
+ //is ip
+ Error err = tcp_connection->connect(IP_Address(conn_host),p_port);
+ if (err) {
+ status=STATUS_CANT_CONNECT;
+ return err;
+ }
+
+ status=STATUS_CONNECTING;
+ } else {
+ //is hostname
+ resolving=IP::get_singleton()->resolve_hostname_queue_item(conn_host);
+ status=STATUS_RESOLVING;
+
+ }
+
+ return OK;
+}
+
+
+void HTTPClient::set_connection(const Ref<StreamPeer>& p_connection){
+
+ close();
+ connection=p_connection;
+
+}
+
+
+Error HTTPClient::request( Method p_method, const String& p_url, const Vector<String>& p_headers,const String& p_body) {
+
+ ERR_FAIL_INDEX_V(p_method,METHOD_MAX,ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(status!=STATUS_CONNECTED,ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(connection.is_null(),ERR_INVALID_DATA);
+
+
+ static const char* _methods[METHOD_MAX]={
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ "DELETE",
+ "OPTIONS",
+ "TRACE",
+ "CONNECT"};
+
+ String request=String(_methods[p_method])+" "+p_url+" HTTP/1.1\r\n";
+ request+="Host: "+conn_host+":"+itos(conn_port)+"\r\n";
+ for(int i=0;i<p_headers.size();i++) {
+ request+=p_headers[i]+"\r\n";
+ }
+ request+="\r\n";
+ request+=p_body;
+
+ CharString cs=request.utf8();
+ Error err = connection->put_data((const uint8_t*)cs.ptr(),cs.length());
+ if (err) {
+ close();
+ status=STATUS_CONNECTION_ERROR;
+ return err;
+ }
+
+ status=STATUS_REQUESTING;
+
+ return OK;
+}
+
+Error HTTPClient::send_body_text(const String& p_body){
+
+ return OK;
+}
+
+Error HTTPClient::send_body_data(const ByteArray& p_body){
+
+ return OK;
+}
+
+bool HTTPClient::has_response() const {
+
+ return response_headers.size()!=0;
+}
+
+bool HTTPClient::is_response_chunked() const {
+
+ return chunked;
+}
+
+int HTTPClient::get_response_code() const {
+
+ return response_num;
+}
+Error HTTPClient::get_response_headers(List<String> *r_response) {
+
+ if (!response_headers.size())
+ return ERR_INVALID_PARAMETER;
+
+ for(int i=0;i<response_headers.size();i++) {
+
+ r_response->push_back(response_headers[i]);
+ }
+
+ response_headers.clear();
+
+ return OK;
+}
+
+
+void HTTPClient::close(){
+
+ if (tcp_connection->get_status()!=StreamPeerTCP::STATUS_NONE)
+ tcp_connection->disconnect();
+
+ connection.unref();
+ status=STATUS_DISCONNECTED;
+ if (resolving!=IP::RESOLVER_INVALID_ID) {
+
+ IP::get_singleton()->erase_resolve_item(resolving);
+ resolving=IP::RESOLVER_INVALID_ID;
+
+ }
+
+ response_headers.clear();
+ response_str.clear();
+ body_size=0;
+ body_left=0;
+ chunk_left=0;
+ response_num=0;
+}
+
+
+Error HTTPClient::poll(){
+
+ switch(status) {
+
+
+ case STATUS_RESOLVING: {
+ ERR_FAIL_COND_V(resolving==IP::RESOLVER_INVALID_ID,ERR_BUG);
+
+ IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving);
+ switch(rstatus) {
+ case IP::RESOLVER_STATUS_WAITING: return OK; //still resolving
+
+ case IP::RESOLVER_STATUS_DONE: {
+
+ IP_Address host = IP::get_singleton()->get_resolve_item_address(resolving);
+ Error err = tcp_connection->connect(host,conn_port);
+ IP::get_singleton()->erase_resolve_item(resolving);
+ resolving=IP::RESOLVER_INVALID_ID;
+ if (err) {
+ status=STATUS_CANT_CONNECT;
+ return err;
+ }
+
+ status=STATUS_CONNECTING;
+ } break;
+ case IP::RESOLVER_STATUS_NONE:
+ case IP::RESOLVER_STATUS_ERROR: {
+
+ IP::get_singleton()->erase_resolve_item(resolving);
+ resolving=IP::RESOLVER_INVALID_ID;
+ close();
+ status=STATUS_CANT_RESOLVE;
+ return ERR_CANT_RESOLVE;
+ } break;
+
+ }
+ } break;
+ case STATUS_CONNECTING: {
+
+ StreamPeerTCP::Status s = tcp_connection->get_status();
+ switch(s) {
+
+ case StreamPeerTCP::STATUS_CONNECTING: {
+ return OK; //do none
+ } break;
+ case StreamPeerTCP::STATUS_CONNECTED: {
+ status=STATUS_CONNECTED;
+ return OK;
+ } break;
+ case StreamPeerTCP::STATUS_ERROR:
+ case StreamPeerTCP::STATUS_NONE: {
+
+ close();
+ status=STATUS_CANT_CONNECT;
+ return ERR_CANT_CONNECT;
+ } break;
+ }
+ } break;
+ case STATUS_CONNECTED: {
+ //request something please
+ return OK;
+ } break;
+ case STATUS_REQUESTING: {
+
+
+ while(true) {
+ uint8_t byte;
+ int rec=0;
+ Error err = connection->get_partial_data(&byte,1,rec);
+ if (err!=OK) {
+ close();
+ status=STATUS_CONNECTION_ERROR;
+ return ERR_CONNECTION_ERROR;
+ }
+
+ if (rec==0)
+ return OK; //keep trying!
+
+ response_str.push_back(byte);
+ int rs = response_str.size();
+ if (
+ (rs>=2 && response_str[rs-2]=='\n' && response_str[rs-1]=='\n') ||
+ (rs>=4 && response_str[rs-4]=='\r' && response_str[rs-3]=='\n' && rs>=4 && response_str[rs-2]=='\r' && response_str[rs-1]=='\n')
+ ) {
+
+
+ //end of response, parse.
+ response_str.push_back(0);
+ String response;
+ response.parse_utf8((const char*)response_str.ptr());
+ print_line("END OF RESPONSE? :\n"+response+"\n------");
+ Vector<String> responses = response.split("\n");
+ body_size=0;
+ chunked=false;
+ body_left=0;
+ chunk_left=0;
+ response_headers.clear();
+ response_num = RESPONSE_OK;
+
+ for(int i=0;i<responses.size();i++) {
+
+ String s = responses[i].strip_edges();
+ if (s.length()==0)
+ continue;
+ if (s.begins_with("Content-Length:")) {
+ body_size = s.substr(s.find(":")+1,s.length()).strip_edges().to_int();
+ body_left=body_size;
+ }
+
+ if (s.begins_with("Transfer-Encoding:")) {
+ String encoding = s.substr(s.find(":")+1,s.length()).strip_edges();
+ print_line("TRANSFER ENCODING: "+encoding);
+ if (encoding=="chunked") {
+ chunked=true;
+ }
+
+ }
+
+ if (i==0 && responses[i].begins_with("HTTP")) {
+
+ String num = responses[i].get_slice(" ",1);
+ response_num=num.to_int();
+ } else {
+
+ response_headers.push_back(s);
+ }
+
+ }
+
+ if (body_size==0 && !chunked) {
+
+ status=STATUS_CONNECTED; //ask for something again?
+ } else {
+ status=STATUS_BODY;
+ }
+ return OK;
+ }
+ }
+ //wait for response
+ return OK;
+ } break;
+ case STATUS_DISCONNECTED: {
+ return ERR_UNCONFIGURED;
+ } break;
+ case STATUS_CONNECTION_ERROR: {
+ return ERR_CONNECTION_ERROR;
+ } break;
+ case STATUS_CANT_CONNECT: {
+ return ERR_CANT_CONNECT;
+ } break;
+ case STATUS_CANT_RESOLVE: {
+ return ERR_CANT_RESOLVE;
+ } break;
+ }
+
+
+ return OK;
+}
+
+
+Dictionary HTTPClient::_get_response_headers_as_dictionary() {
+
+ List<String> rh;
+ get_response_headers(&rh);
+ Dictionary ret;
+ for(const List<String>::Element *E=rh.front();E;E=E->next()) {
+ String s = E->get();
+ int sp = s.find(":");
+ if (sp==-1)
+ continue;
+ String key = s.substr(0,sp).strip_edges();
+ String value = s.substr(sp+1,s.length()).strip_edges();
+ ret[key]=value;
+
+ }
+
+ return ret;
+}
+
+StringArray HTTPClient::_get_response_headers() {
+
+ List<String> rh;
+ get_response_headers(&rh);
+ StringArray ret;
+ ret.resize(rh.size());
+ int idx=0;
+ for(const List<String>::Element *E=rh.front();E;E=E->next()) {
+ ret.set(idx++,E->get());
+ }
+
+ return ret;
+}
+
+int HTTPClient::get_response_body_length() const {
+
+ return body_size;
+}
+
+ByteArray HTTPClient::read_response_body_chunk() {
+
+ ERR_FAIL_COND_V( status !=STATUS_BODY, ByteArray() );
+
+ Error err=OK;
+
+ if (chunked) {
+
+ while(true) {
+
+ if (chunk_left==0) {
+ //reading len
+ uint8_t b;
+ int rec=0;
+ err = connection->get_partial_data(&b,1,rec);
+
+ if (rec==0)
+ break;
+
+ chunk.push_back(b);
+
+ if (chunk.size()>32) {
+ ERR_PRINT("HTTP Invalid chunk hex len");
+ status=STATUS_CONNECTION_ERROR;
+ return ByteArray();
+ }
+
+ if (chunk.size()>2 && chunk[chunk.size()-2]=='\r' && chunk[chunk.size()-1]=='\n') {
+
+ int len=0;
+ for(int i=0;i<chunk.size()-2;i++) {
+ char c = chunk[i];
+ int v=0;
+ if (c>='0' && c<='9')
+ v=c-'0';
+ else if (c>='a' && c<='f')
+ v=c-'a'+10;
+ else if (c>='A' && c<='F')
+ v=c-'A'+10;
+ else {
+ ERR_PRINT("HTTP Chunk len not in hex!!");
+ status=STATUS_CONNECTION_ERROR;
+ return ByteArray();
+ }
+ len<<=4;
+ len|=v;
+ if (len>(1<<24)) {
+ ERR_PRINT("HTTP Chunk too big!! >16mb");
+ status=STATUS_CONNECTION_ERROR;
+ return ByteArray();
+ }
+
+ }
+
+ if (len==0) {
+ //end!
+ status=STATUS_CONNECTED;
+ chunk.clear();
+ return ByteArray();
+ }
+
+ chunk_left=len+2;
+ chunk.resize(chunk_left);
+
+ }
+ } else {
+
+ int rec=0;
+ err = connection->get_partial_data(&chunk[chunk.size()-chunk_left],chunk_left,rec);
+ if (rec==0) {
+ break;
+ }
+ chunk_left-=rec;
+
+ if (chunk_left==0) {
+
+ if (chunk[chunk.size()-2]!='\r' || chunk[chunk.size()-1]!='\n') {
+ ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)");
+ status=STATUS_CONNECTION_ERROR;
+ return ByteArray();
+ }
+
+ ByteArray ret;
+ ret.resize(chunk.size()-2);
+ {
+ ByteArray::Write w = ret.write();
+ copymem(w.ptr(),chunk.ptr(),chunk.size()-2);
+ }
+ chunk.clear();
+
+ return ret;
+
+ }
+
+ break;
+ }
+ }
+
+ } else {
+ ByteArray::Write r = tmp_read.write();
+ int rec=0;
+ err = connection->get_partial_data(r.ptr(),MIN(body_left,tmp_read.size()),rec);
+ if (rec>0) {
+ ByteArray ret;
+ ret.resize(rec);
+ ByteArray::Write w = ret.write();
+ copymem(w.ptr(),r.ptr(),rec);
+ body_left-=rec;
+ if (body_left==0) {
+ status=STATUS_CONNECTED;
+ }
+ return ret;
+ }
+
+ }
+
+
+ if (err!=OK) {
+ close();
+ if (err==ERR_FILE_EOF) {
+
+ status=STATUS_DISCONNECTED; //server disconnected
+ } else {
+
+ status=STATUS_CONNECTION_ERROR;
+ }
+ } else if (body_left==0 && !chunked) {
+
+ status=STATUS_CONNECTED;
+ }
+
+ return ByteArray();
+}
+
+HTTPClient::Status HTTPClient::get_status() const {
+
+
+ return status;
+}
+
+void HTTPClient::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("connect:Error","host","port"),&HTTPClient::connect);
+ ObjectTypeDB::bind_method(_MD("set_connection","connection:StreamPeer"),&HTTPClient::set_connection);
+ ObjectTypeDB::bind_method(_MD("request","method","url","headers","body"),&HTTPClient::request,DEFVAL(String()));
+ ObjectTypeDB::bind_method(_MD("send_body_text","body"),&HTTPClient::send_body_text);
+ ObjectTypeDB::bind_method(_MD("send_body_data","body"),&HTTPClient::send_body_data);
+ ObjectTypeDB::bind_method(_MD("close"),&HTTPClient::close);
+
+ ObjectTypeDB::bind_method(_MD("has_response"),&HTTPClient::has_response);
+ ObjectTypeDB::bind_method(_MD("is_response_chunked"),&HTTPClient::is_response_chunked);
+ ObjectTypeDB::bind_method(_MD("get_response_code"),&HTTPClient::get_response_code);
+ ObjectTypeDB::bind_method(_MD("get_response_headers"),&HTTPClient::_get_response_headers);
+ ObjectTypeDB::bind_method(_MD("get_response_headers_as_dictionary"),&HTTPClient::_get_response_headers_as_dictionary);
+ ObjectTypeDB::bind_method(_MD("get_response_body_length"),&HTTPClient::get_response_body_length);
+ ObjectTypeDB::bind_method(_MD("read_response_body_chunk"),&HTTPClient::read_response_body_chunk);
+
+ ObjectTypeDB::bind_method(_MD("get_status"),&HTTPClient::get_status);
+ ObjectTypeDB::bind_method(_MD("poll:Error"),&HTTPClient::poll);
+
+ BIND_CONSTANT( METHOD_GET );
+ BIND_CONSTANT( METHOD_HEAD );
+ BIND_CONSTANT( METHOD_POST );
+ BIND_CONSTANT( METHOD_PUT );
+ BIND_CONSTANT( METHOD_DELETE );
+ BIND_CONSTANT( METHOD_OPTIONS );
+ BIND_CONSTANT( METHOD_TRACE );
+ BIND_CONSTANT( METHOD_CONNECT );
+ BIND_CONSTANT( METHOD_MAX );
+
+ BIND_CONSTANT( STATUS_DISCONNECTED );
+ BIND_CONSTANT( STATUS_RESOLVING ); //resolving hostname (if passed a hostname)
+ BIND_CONSTANT( STATUS_CANT_RESOLVE );
+ BIND_CONSTANT( STATUS_CONNECTING ); //connecting to ip
+ BIND_CONSTANT( STATUS_CANT_CONNECT );
+ BIND_CONSTANT( STATUS_CONNECTED ); //connected ); requests only accepted here
+ BIND_CONSTANT( STATUS_REQUESTING ); // request in progress
+ BIND_CONSTANT( STATUS_BODY ); // request resulted in body ); which must be read
+ BIND_CONSTANT( STATUS_CONNECTION_ERROR );
+
+
+ BIND_CONSTANT( RESPONSE_CONTINUE );
+ BIND_CONSTANT( RESPONSE_SWITCHING_PROTOCOLS );
+ BIND_CONSTANT( RESPONSE_PROCESSING );
+
+ // 2xx successful
+ BIND_CONSTANT( RESPONSE_OK );
+ BIND_CONSTANT( RESPONSE_CREATED );
+ BIND_CONSTANT( RESPONSE_ACCEPTED );
+ BIND_CONSTANT( RESPONSE_NON_AUTHORITATIVE_INFORMATION );
+ BIND_CONSTANT( RESPONSE_NO_CONTENT );
+ BIND_CONSTANT( RESPONSE_RESET_CONTENT );
+ BIND_CONSTANT( RESPONSE_PARTIAL_CONTENT );
+ BIND_CONSTANT( RESPONSE_MULTI_STATUS );
+ BIND_CONSTANT( RESPONSE_IM_USED );
+
+ // 3xx redirection
+ BIND_CONSTANT( RESPONSE_MULTIPLE_CHOICES );
+ BIND_CONSTANT( RESPONSE_MOVED_PERMANENTLY );
+ BIND_CONSTANT( RESPONSE_FOUND );
+ BIND_CONSTANT( RESPONSE_SEE_OTHER );
+ BIND_CONSTANT( RESPONSE_NOT_MODIFIED );
+ BIND_CONSTANT( RESPONSE_USE_PROXY );
+ BIND_CONSTANT( RESPONSE_TEMPORARY_REDIRECT );
+
+ // 4xx client error
+ BIND_CONSTANT( RESPONSE_BAD_REQUEST );
+ BIND_CONSTANT( RESPONSE_UNAUTHORIZED );
+ BIND_CONSTANT( RESPONSE_PAYMENT_REQUIRED );
+ BIND_CONSTANT( RESPONSE_FORBIDDEN );
+ BIND_CONSTANT( RESPONSE_NOT_FOUND );
+ BIND_CONSTANT( RESPONSE_METHOD_NOT_ALLOWED );
+ BIND_CONSTANT( RESPONSE_NOT_ACCEPTABLE );
+ BIND_CONSTANT( RESPONSE_PROXY_AUTHENTICATION_REQUIRED );
+ BIND_CONSTANT( RESPONSE_REQUEST_TIMEOUT );
+ BIND_CONSTANT( RESPONSE_CONFLICT );
+ BIND_CONSTANT( RESPONSE_GONE );
+ BIND_CONSTANT( RESPONSE_LENGTH_REQUIRED );
+ BIND_CONSTANT( RESPONSE_PRECONDITION_FAILED );
+ BIND_CONSTANT( RESPONSE_REQUEST_ENTITY_TOO_LARGE );
+ BIND_CONSTANT( RESPONSE_REQUEST_URI_TOO_LONG );
+ BIND_CONSTANT( RESPONSE_UNSUPPORTED_MEDIA_TYPE );
+ BIND_CONSTANT( RESPONSE_REQUESTED_RANGE_NOT_SATISFIABLE );
+ BIND_CONSTANT( RESPONSE_EXPECTATION_FAILED );
+ BIND_CONSTANT( RESPONSE_UNPROCESSABLE_ENTITY );
+ BIND_CONSTANT( RESPONSE_LOCKED );
+ BIND_CONSTANT( RESPONSE_FAILED_DEPENDENCY );
+ BIND_CONSTANT( RESPONSE_UPGRADE_REQUIRED );
+
+ // 5xx server error
+ BIND_CONSTANT( RESPONSE_INTERNAL_SERVER_ERROR );
+ BIND_CONSTANT( RESPONSE_NOT_IMPLEMENTED );
+ BIND_CONSTANT( RESPONSE_BAD_GATEWAY );
+ BIND_CONSTANT( RESPONSE_SERVICE_UNAVAILABLE );
+ BIND_CONSTANT( RESPONSE_GATEWAY_TIMEOUT );
+ BIND_CONSTANT( RESPONSE_HTTP_VERSION_NOT_SUPPORTED );
+ BIND_CONSTANT( RESPONSE_INSUFFICIENT_STORAGE );
+ BIND_CONSTANT( RESPONSE_NOT_EXTENDED );
+
+}
+
+HTTPClient::HTTPClient(){
+
+ tcp_connection = StreamPeerTCP::create();
+ resolving = IP::RESOLVER_INVALID_ID;
+ status=STATUS_DISCONNECTED;
+ conn_port=80;
+ body_size=0;
+ chunked=false;
+ body_left=0;
+ chunk_left=0;
+ response_num=0;
+
+ tmp_read.resize(4096);
+}
+
+HTTPClient::~HTTPClient(){
+
+
+}
+
+