/********************************************************************************* Copyright 2006-2008 MakingThings Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *********************************************************************************/ #include "config.h" #ifdef MAKE_CTRL_NETWORK #include "stdlib.h" #include "webclient.h" #include "lwip/api.h" #define WEBCLIENT_INTERNAL_BUFFER_SIZE 200 char WebClient_InternalBuffer[ WEBCLIENT_INTERNAL_BUFFER_SIZE ]; /** \defgroup webclient Web Client A very simple web client for HTTP operations. The web client system allows the Make Controller to get/post data to a webserver. This makes it straightforward to use the Make Controller as a source of data for your web apps. Note that these functions make liberal use of printf-style functions, which can require lots of memory to be allocated to the task calling them. There's currently not a method provided for name resolution - you can always ping the server you want to communicate with to see its IP address, and just use that. See Network_DnsGetHostByName() for a way to get the address of a particular web site. \ingroup Libraries @{ */ /** Performs an HTTP GET operation to the path at the address / port specified. Reads through the HTTP header and copies the data into the buffer you pass in. Because sites can often be slow in their responses, this will wait up to 1 second (in 100 ms. intervals) for data to become available. Some websites seem to reject connections occassionally - perhaps because we don't supply as much info to the server as a browser might, for example. Simpler websites should be just fine. Note that this uses lots of printf style functions and may require a fair amount of memory to be allocated to the task calling it. The result is returned in the specified buffer. @param address The IP address of the server to get from. Usually created using the IP_ADDRESS( ) macro. @param port The port to connect on. Usually 80 for HTTP. @param hostname A string specifying the name of the host to connect to. When connecting to a server that does shared hosting, this will specify who to connect with. @param path The path on the server to connect to. @param buffer A pointer to the buffer read back into. @param buffer_size An integer specifying the actual size of the buffer. @return the number of bytes read, or < 0 on error. \par Example \code int addr = IP_ADDRESS( 72, 249, 53, 185); // makingthings.com is 72.249.53.185 int bufLength = 100; char myBuffer[bufLength]; int getSize = WebClient_Get( addr, 80, "www.makingthings.com", "/test/path", myBuffer, bufLength ); \endcode Now we should have the results of the HTTP GET from \b www.makingthings.com/test/path in \b myBuffer. */ int WebClient_Get( int address, int port, char* hostname, char* path, char* buffer, int buffer_size ) { char* b = WebClient_InternalBuffer; struct netconn *s = Socket( address, port ); if ( s != NULL ) { // construct the GET request int send_len = snprintf( b, WEBCLIENT_INTERNAL_BUFFER_SIZE, "GET %s HTTP/1.1\r\n%s%s%s\r\n", path, ( hostname != NULL ) ? "Host: " : "", ( hostname != NULL ) ? hostname : "", ( hostname != NULL ) ? "\r\n" : "" ); if ( send_len > WEBCLIENT_INTERNAL_BUFFER_SIZE ) { SocketClose( s ); return CONTROLLER_ERROR_INSUFFICIENT_RESOURCES; } // send the GET request if(!SocketWrite( s, b, send_len )) { SocketClose( s ); return CONTROLLER_ERROR_WRITE_FAILED; } int content_length = 0; // read through the response header to get to the data, and pick up the content-length as we go int buffer_length; while ( ( buffer_length = SocketReadLine( s, b, WEBCLIENT_INTERNAL_BUFFER_SIZE ) ) ) { if ( strncmp( b, "\r\n", 2 ) == 0 ) break; if ( strncmp( b, "Content-Length", 14 ) == 0 ) content_length = atoi( &b[ 15 ] ); } // read the data into the given buffer until there's none left, or the passed in buffer is full int total_bytes_read = 0; int buf_remaining = buffer_size; if ( content_length > 0 && buffer_length > 0 ) { char* bp = buffer; while( total_bytes_read < buffer_size && total_bytes_read < content_length ) { int avail = SocketBytesAvailable(s); if(!avail) // sometimes the connection can be slooooow, sleep a bit and try again { int times = 10; while(times--) { Sleep(100); if((avail = SocketBytesAvailable(s))) break; } } if(!avail) // if we still didn't get anything, bail break; if(avail > buf_remaining) // make sure we don't read more than can fit avail = buf_remaining; buffer_length = SocketRead( s, bp, avail ); if(!buffer_length) // this will be 0 when we get a read error - bail in that case break; // update counts buf_remaining -= buffer_length; total_bytes_read += buffer_length; bp += buffer_length; } } SocketClose( s ); return total_bytes_read; } else return CONTROLLER_ERROR_BAD_ADDRESS; } /** Performs an HTTP POST operation to the path at the address / port specified. The actual post contents are found read from a given buffer and the result is returned in the same buffer. @param address The IP address of the server to post to. @param port The port on the server you're connecting to. Usually 80 for HTTP. @param hostname A string specifying the name of the host to connect to. When connecting to a server that does shared hosting, this will specify who to connect with. @param path The path on the server to post to. @param buffer A pointer to the buffer to write from and read back into. @param buffer_length An integer specifying the number of bytes to write. @param buffer_size An integer specifying the actual size of the buffer. @return status. \par Example \code // we'll post a test message to www.makingthings.com/post/path int addr = IP_ADDRESS( 72, 249, 53, 185); // makingthings.com is 72.249.53.185 int bufLength = 100; char myBuffer[bufLength]; sprintf( myBuffer, "A test message to post" ); int result = WebClient_Post( addr, 80, "www.makingthings.com", "/post/path", myBuffer, strlen("A test message to post"), bufLength ); \endcode */ int WebClient_Post( int address, int port, char* hostname, char* path, char* buffer, int buffer_length, int buffer_size ) { char* b = WebClient_InternalBuffer; int buffer_read = 0; int wrote = 0; void* s = Socket( address, port ); if ( s != NULL ) { int send_len = snprintf( b, WEBCLIENT_INTERNAL_BUFFER_SIZE, "POST %s HTTP/1.1\r\n%s%s%sContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n", path, ( hostname != NULL ) ? "Host: " : "", ( hostname != NULL ) ? hostname : "", ( hostname != NULL ) ? "\r\n" : "", buffer_length ); if ( send_len > WEBCLIENT_INTERNAL_BUFFER_SIZE ) { SocketClose( s ); return CONTROLLER_ERROR_INSUFFICIENT_RESOURCES; } wrote = SocketWrite( s, b, send_len ); if ( wrote == 0 ) { SocketClose( s ); return CONTROLLER_ERROR_WRITE_FAILED; } SocketWrite( s, buffer, buffer_length ); int content_length = 0; int b_len; while ( ( b_len = SocketReadLine( s, b, WEBCLIENT_INTERNAL_BUFFER_SIZE ) ) ) { if ( strncmp( b, "\r\n", 2 ) == 0 ) break; if ( strncmp( b, "Content-Length", 14 ) == 0 ) content_length = atoi( &b[ 16 ] ); } if ( content_length > 0 && b_len > 0 ) { char* bp = buffer; while ( ( b_len = SocketRead( s, bp, buffer_size - buffer_read ) ) ) { buffer_read += b_len; bp += b_len; if ( buffer_read >= content_length ) break; } } SocketClose( s ); return buffer_read; } else return CONTROLLER_ERROR_BAD_ADDRESS; } /** @} */ #endif // MAKE_CTRL_NETWORK