]> git.piffa.net Git - arduino/blob - books/pdummies/Libraries/HttpClient/HttpClient.cpp
first commit
[arduino] / books / pdummies / Libraries / HttpClient / HttpClient.cpp
1 // Class to simplify HTTP fetching on Arduino
2 // (c) Copyright 2010-2011 MCQN Ltd
3 // Released under Apache License, version 2.0
4
5 #include "HttpClient.h"
6 #include "b64.h"
7 #ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet
8 #include <Dns.h>
9 #endif
10 #include <string.h>
11 #include <ctype.h>
12
13 // Initialize constants
14 const char* HttpClient::kUserAgent = "Arduino/2.0";
15 const char* HttpClient::kGet = "GET";
16 const char* HttpClient::kPost = "POST";
17 const char* HttpClient::kPut = "PUT";
18 const char* HttpClient::kDelete = "DELETE";
19 const char* HttpClient::kContentLengthPrefix = "Content-Length: ";
20
21 #ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet
22 HttpClient::HttpClient(Client& aClient, const char* aProxy, uint16_t aProxyPort)
23  : iClient(&aClient), iProxyPort(aProxyPort)
24 {
25   resetState();
26   if (aProxy)
27   {
28     // Resolve the IP address for the proxy
29     DNSClient dns;
30     dns.begin(Ethernet.dnsServerIP());
31     // Not ideal that we discard any errors here, but not a lot we can do in the ctor
32     // and we'll get a connect error later anyway
33     (void)dns.getHostByName(aProxy, iProxyAddress);
34   }
35 }
36 #else
37 HttpClient::HttpClient(Client& aClient)
38  : iClient(&aClient), iProxyPort(0)
39 {
40   resetState();
41 }
42 #endif
43
44 void HttpClient::resetState()
45 {
46   iState = eIdle;
47   iStatusCode = 0;
48   iContentLength = 0;
49   iBodyLengthConsumed = 0;
50   iContentLengthPtr = 0;
51 }
52
53 void HttpClient::stop()
54 {
55   iClient->stop();
56   resetState();
57 }
58
59 void HttpClient::beginRequest()
60 {
61   iState = eRequestStarted;
62 }
63
64 int HttpClient::startRequest(const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent)
65 {
66     tHttpState initialState = iState;
67     if ((eIdle != iState) && (eRequestStarted != iState))
68     {
69         return HTTP_ERROR_API;
70     }
71
72     if (iProxyPort)
73     {
74         if (!iClient->connect(iProxyAddress, iProxyPort) > 0)
75         {
76 #ifdef LOGGING
77             Serial.println("Proxy connection failed");
78 #endif
79             return HTTP_ERROR_CONNECTION_FAILED;
80         }
81     }
82     else
83     {
84         if (!iClient->connect(aServerName, aServerPort) > 0)
85         {
86 #ifdef LOGGING
87             Serial.println("Connection failed");
88 #endif
89             return HTTP_ERROR_CONNECTION_FAILED;
90         }
91     }
92
93     // Now we're connected, send the first part of the request
94     int ret = sendInitialHeaders(aServerName, IPAddress(0,0,0,0), aServerPort, aURLPath, aHttpMethod, aUserAgent);
95     if ((initialState == eIdle) && (HTTP_SUCCESS == ret))
96     {
97         // This was a simple version of the API, so terminate the headers now
98         finishHeaders();
99     }
100     // else we'll call it in endRequest or in the first call to print, etc.
101
102     return ret;
103 }
104
105 int HttpClient::startRequest(const IPAddress& aServerAddress, const char* aServerName, uint16_t aServerPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent)
106 {
107     tHttpState initialState = iState;
108     if ((eIdle != iState) && (eRequestStarted != iState))
109     {
110         return HTTP_ERROR_API;
111     }
112
113     if (iProxyPort)
114     {
115         if (!iClient->connect(iProxyAddress, iProxyPort) > 0)
116         {
117 #ifdef LOGGING
118             Serial.println("Proxy connection failed");
119 #endif
120             return HTTP_ERROR_CONNECTION_FAILED;
121         }
122     }
123     else
124     {
125         if (!iClient->connect(aServerAddress, aServerPort) > 0)
126         {
127 #ifdef LOGGING
128             Serial.println("Connection failed");
129 #endif
130             return HTTP_ERROR_CONNECTION_FAILED;
131         }
132     }
133
134     // Now we're connected, send the first part of the request
135     int ret = sendInitialHeaders(aServerName, aServerAddress, aServerPort, aURLPath, aHttpMethod, aUserAgent);
136     if ((initialState == eIdle) && (HTTP_SUCCESS == ret))
137     {
138         // This was a simple version of the API, so terminate the headers now
139         finishHeaders();
140     }
141     // else we'll call it in endRequest or in the first call to print, etc.
142
143     return ret;
144 }
145
146 int HttpClient::sendInitialHeaders(const char* aServerName, IPAddress aServerIP, uint16_t aPort, const char* aURLPath, const char* aHttpMethod, const char* aUserAgent)
147 {
148 #ifdef LOGGING
149     Serial.println("Connected");
150 #endif
151     // Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0"
152     iClient->print(aHttpMethod);
153     iClient->print(" ");
154     if (iProxyPort)
155     {
156       // We're going through a proxy, send a full URL
157       iClient->print("http://");
158       if (aServerName)
159       {
160         // We've got a server name, so use it
161         iClient->print(aServerName);
162       }
163       else
164       {
165         // We'll have to use the IP address
166         iClient->print(aServerIP);
167       }
168       if (aPort != kHttpPort)
169       {
170         iClient->print(":");
171         iClient->print(aPort);
172       }
173     }
174     iClient->print(aURLPath);
175     iClient->println(" HTTP/1.1");
176     // The host header, if required
177     if (aServerName)
178     {
179         iClient->print("Host: ");
180         iClient->print(aServerName);
181         if (aPort != kHttpPort)
182         {
183           iClient->print(":");
184           iClient->print(aPort);
185         }
186         iClient->println();
187     }
188     // And user-agent string
189     iClient->print("User-Agent: ");
190     if (aUserAgent)
191     {
192         iClient->println(aUserAgent);
193     }
194     else
195     {
196         iClient->println(kUserAgent);
197     }
198
199     // Everything has gone well
200     iState = eRequestStarted;
201     return HTTP_SUCCESS;
202 }
203
204 void HttpClient::sendHeader(const char* aHeader)
205 {
206     iClient->println(aHeader);
207 }
208
209 void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue)
210 {
211     iClient->print(aHeaderName);
212     iClient->print(": ");
213     iClient->println(aHeaderValue);
214 }
215
216 void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue)
217 {
218     iClient->print(aHeaderName);
219     iClient->print(": ");
220     iClient->println(aHeaderValue);
221 }
222
223 void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword)
224 {
225     // Send the initial part of this header line
226     iClient->print("Authorization: Basic ");
227     // Now Base64 encode "aUser:aPassword" and send that
228     // This seems trickier than it should be but it's mostly to avoid either
229     // (a) some arbitrarily sized buffer which hopes to be big enough, or
230     // (b) allocating and freeing memory
231     // ...so we'll loop through 3 bytes at a time, outputting the results as we
232     // go.
233     // In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data
234     unsigned char input[3];
235     unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print
236     int userLen = strlen(aUser);
237     int passwordLen = strlen(aPassword);
238     int inputOffset = 0;
239     for (int i = 0; i < (userLen+1+passwordLen); i++)
240     {
241         // Copy the relevant input byte into the input
242         if (i < userLen)
243         {
244             input[inputOffset++] = aUser[i];
245         }
246         else if (i == userLen)
247         {
248             input[inputOffset++] = ':';
249         }
250         else
251         {
252             input[inputOffset++] = aPassword[i-(userLen+1)];
253         }
254         // See if we've got a chunk to encode
255         if ( (inputOffset == 3) || (i == userLen+passwordLen) )
256         {
257             // We've either got to a 3-byte boundary, or we've reached then end
258             b64_encode(input, inputOffset, output, 4);
259             // NUL-terminate the output string
260             output[4] = '\0';
261             // And write it out
262             iClient->print((char*)output);
263 // FIXME We might want to fill output with '=' characters if b64_encode doesn't
264 // FIXME do it for us when we're encoding the final chunk
265             inputOffset = 0;
266         }
267     }
268     // And end the header we've sent
269     iClient->println();
270 }
271
272 void HttpClient::finishHeaders()
273 {
274     iClient->println();
275     iState = eRequestSent;
276 }
277
278 void HttpClient::endRequest()
279 {
280     if (iState < eRequestSent)
281     {
282         // We still need to finish off the headers
283         finishHeaders();
284     }
285     // else the end of headers has already been sent, so nothing to do here
286 }
287
288 int HttpClient::responseStatusCode()
289 {
290     if (iState < eRequestSent)
291     {
292         return HTTP_ERROR_API;
293     }
294     // The first line will be of the form Status-Line:
295     //   HTTP-Version SP Status-Code SP Reason-Phrase CRLF
296     // Where HTTP-Version is of the form:
297     //   HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
298
299     char c = '\0';
300     do
301     {
302         // Make sure the status code is reset, and likewise the state.  This
303         // lets us easily cope with 1xx informational responses by just
304         // ignoring them really, and reading the next line for a proper response
305         iStatusCode = 0;
306         iState = eRequestSent;
307
308         unsigned long timeoutStart = millis();
309         // Psuedo-regexp we're expecting before the status-code
310         const char* statusPrefix = "HTTP/*.* ";
311         const char* statusPtr = statusPrefix;
312         // Whilst we haven't timed out & haven't reached the end of the headers
313         while ((c != '\n') && 
314                ( (millis() - timeoutStart) < kHttpResponseTimeout ))
315         {
316             if (available())
317             {
318                 c = read();
319                 if (c != -1)
320                 {
321                     switch(iState)
322                     {
323                     case eRequestSent:
324                         // We haven't reached the status code yet
325                         if ( (*statusPtr == '*') || (*statusPtr == c) )
326                         {
327                             // This character matches, just move along
328                             statusPtr++;
329                             if (*statusPtr == '\0')
330                             {
331                                 // We've reached the end of the prefix
332                                 iState = eReadingStatusCode;
333                             }
334                         }
335                         else
336                         {
337                             return HTTP_ERROR_INVALID_RESPONSE;
338                         }
339                         break;
340                     case eReadingStatusCode:
341                         if (isdigit(c))
342                         {
343                             // This assumes we won't get more than the 3 digits we
344                             // want
345                             iStatusCode = iStatusCode*10 + (c - '0');
346                         }
347                         else
348                         {
349                             // We've reached the end of the status code
350                             // We could sanity check it here or double-check for ' '
351                             // rather than anything else, but let's be lenient
352                             iState = eStatusCodeRead;
353                         }
354                         break;
355                     case eStatusCodeRead:
356                         // We're just waiting for the end of the line now
357                         break;
358                     };
359                     // We read something, reset the timeout counter
360                     timeoutStart = millis();
361                 }
362             }
363             else
364             {
365                 // We haven't got any data, so let's pause to allow some to
366                 // arrive
367                 delay(kHttpWaitForDataDelay);
368             }
369         }
370         if ( (c == '\n') && (iStatusCode < 200) )
371         {
372             // We've reached the end of an informational status line
373             c = '\0'; // Clear c so we'll go back into the data reading loop
374         }
375     }
376     // If we've read a status code successfully but it's informational (1xx)
377     // loop back to the start
378     while ( (iState == eStatusCodeRead) && (iStatusCode < 200) );
379
380     if ( (c == '\n') && (iState == eStatusCodeRead) )
381     {
382         // We've read the status-line successfully
383         return iStatusCode;
384     }
385     else if (c != '\n')
386     {
387         // We must've timed out before we reached the end of the line
388         return HTTP_ERROR_TIMED_OUT;
389     }
390     else
391     {
392         // This wasn't a properly formed status line, or at least not one we
393         // could understand
394         return HTTP_ERROR_INVALID_RESPONSE;
395     }
396 }
397
398 int HttpClient::skipResponseHeaders()
399 {
400     // Just keep reading until we finish reading the headers or time out
401     unsigned long timeoutStart = millis();
402     // Whilst we haven't timed out & haven't reached the end of the headers
403     while ((!endOfHeadersReached()) && 
404            ( (millis() - timeoutStart) < kHttpResponseTimeout ))
405     {
406         if (available())
407         {
408             (void)readHeader();
409             // We read something, reset the timeout counter
410             timeoutStart = millis();
411         }
412         else
413         {
414             // We haven't got any data, so let's pause to allow some to
415             // arrive
416             delay(kHttpWaitForDataDelay);
417         }
418     }
419     if (endOfHeadersReached())
420     {
421         // Success
422         return HTTP_SUCCESS;
423     }
424     else
425     {
426         // We must've timed out
427         return HTTP_ERROR_TIMED_OUT;
428     }
429 }
430
431 bool HttpClient::endOfBodyReached()
432 {
433     if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader))
434     {
435         // We've got to the body and we know how long it will be
436         return (iBodyLengthConsumed >= contentLength());
437     }
438     return false;
439 }
440
441 int HttpClient::read()
442 {
443 #if 0 // Fails on WiFi because multi-byte read seems to be broken
444     uint8_t b[1];
445     int ret = read(b, 1);
446     if (ret == 1)
447     {
448         return b[0];
449     }
450     else
451     {
452         return -1;
453     }
454 #else
455     int ret = iClient->read();
456     if (ret >= 0)
457     {
458         if (endOfHeadersReached() && iContentLength > 0)
459         {
460             // We're outputting the body now and we've seen a Content-Length header
461             // So keep track of how many bytes are left
462             iBodyLengthConsumed++;
463         }
464     }
465     return ret;
466 #endif
467 }
468
469 int HttpClient::read(uint8_t *buf, size_t size)
470 {
471     int ret =iClient->read(buf, size);
472     if (endOfHeadersReached() && iContentLength > 0)
473     {
474         // We're outputting the body now and we've seen a Content-Length header
475         // So keep track of how many bytes are left
476         if (ret >= 0)
477         {
478             iBodyLengthConsumed += ret;
479         }
480     }
481     return ret;
482 }
483
484 int HttpClient::readHeader()
485 {
486     char c = read();
487
488     if (endOfHeadersReached())
489     {
490         // We've passed the headers, but rather than return an error, we'll just
491         // act as a slightly less efficient version of read()
492         return c;
493     }
494
495     // Whilst reading out the headers to whoever wants them, we'll keep an
496     // eye out for the "Content-Length" header
497     switch(iState)
498     {
499     case eStatusCodeRead:
500         // We're at the start of a line, or somewhere in the middle of reading
501         // the Content-Length prefix
502         if (*iContentLengthPtr == c)
503         {
504             // This character matches, just move along
505             iContentLengthPtr++;
506             if (*iContentLengthPtr == '\0')
507             {
508                 // We've reached the end of the prefix
509                 iState = eReadingContentLength;
510                 // Just in case we get multiple Content-Length headers, this
511                 // will ensure we just get the value of the last one
512                 iContentLength = 0;
513             }
514         }
515         else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r'))
516         {
517             // We've found a '\r' at the start of a line, so this is probably
518             // the end of the headers
519             iState = eLineStartingCRFound;
520         }
521         else
522         {
523             // This isn't the Content-Length header, skip to the end of the line
524             iState = eSkipToEndOfHeader;
525         }
526         break;
527     case eReadingContentLength:
528         if (isdigit(c))
529         {
530             iContentLength = iContentLength*10 + (c - '0');
531         }
532         else
533         {
534             // We've reached the end of the content length
535             // We could sanity check it here or double-check for "\r\n"
536             // rather than anything else, but let's be lenient
537             iState = eSkipToEndOfHeader;
538         }
539         break;
540     case eLineStartingCRFound:
541         if (c == '\n')
542         {
543             iState = eReadingBody;
544         }
545         break;
546     default:
547         // We're just waiting for the end of the line now
548         break;
549     };
550
551     if ( (c == '\n') && !endOfHeadersReached() )
552     {
553         // We've got to the end of this line, start processing again
554         iState = eStatusCodeRead;
555         iContentLengthPtr = kContentLengthPrefix;
556     }
557     // And return the character read to whoever wants it
558     return c;
559 }
560
561
562