1 // Class to simplify HTTP fetching on Arduino
2 // (c) Copyright MCQN Ltd. 2010-2012
3 // Released under Apache License, version 2.0
12 static const int HTTP_SUCCESS =0;
13 // The end of the headers has been reached. This consumes the '\n'
14 // Could not connect to the server
15 static const int HTTP_ERROR_CONNECTION_FAILED =-1;
16 // This call was made when the HttpClient class wasn't expecting it
17 // to be called. Usually indicates your code is using the class
19 static const int HTTP_ERROR_API =-2;
20 // Spent too long waiting for a reply
21 static const int HTTP_ERROR_TIMED_OUT =-3;
22 // The response from the server is invalid, is it definitely an HTTP
24 static const int HTTP_ERROR_INVALID_RESPONSE =-4;
26 class HttpClient : public Client
29 static const int kNoContentLengthHeader =-1;
30 static const int kHttpPort =80;
31 static const char* kUserAgent;
32 static const char* kGet;
33 static const char* kPost;
34 static const char* kPut;
35 static const char* kDelete;
37 // FIXME Write longer API request, using port and user-agent, example
38 // FIXME Update tempToPachube example to calculate Content-Length correctly
40 #ifdef PROXY_ENABLED // currently disabled as introduces dependency on Dns.h in Ethernet
41 HttpClient(Client& aClient, const char* aProxy =NULL, uint16_t aProxyPort =0);
43 HttpClient(Client& aClient);
46 /** Start a more complex request.
47 Use this when you need to send additional headers in the request,
48 but you will also need to call endRequest() when you are finished.
52 /** End a more complex request.
53 Use this when you need to have sent additional headers in the request,
54 but you will also need to call beginRequest() at the start.
58 /** Connect to the server and start to send a GET request.
59 @param aServerName Name of the server being connected to. If NULL, the
60 "Host" header line won't be sent
61 @param aServerPort Port to connect to on the server
62 @param aURLPath Url to request
63 @param aUserAgent User-Agent string to send. If NULL the default
64 user-agent kUserAgent will be sent
65 @return 0 if successful, else error
67 int get(const char* aServerName, uint16_t aServerPort, const char* aURLPath,
68 const char* aUserAgent =NULL)
69 { return startRequest(aServerName, aServerPort, aURLPath, kGet, aUserAgent); }
71 /** Connect to the server and start to send a GET request.
72 @param aServerName Name of the server being connected to. If NULL, the
73 "Host" header line won't be sent
74 @param aURLPath Url to request
75 @param aUserAgent User-Agent string to send. If NULL the default
76 user-agent kUserAgent will be sent
77 @return 0 if successful, else error
79 int get(const char* aServerName, const char* aURLPath, const char* aUserAgent =NULL)
80 { return startRequest(aServerName, kHttpPort, aURLPath, kGet, aUserAgent); }
82 /** Connect to the server and start to send a GET request. This version connects
83 doesn't perform a DNS lookup and just connects to the given IP address.
84 @param aServerAddress IP address of the server to connect to
85 @param aServerName Name of the server being connected to. If NULL, the
86 "Host" header line won't be sent
87 @param aServerPort Port to connect to on the server
88 @param aURLPath Url to request
89 @param aUserAgent User-Agent string to send. If NULL the default
90 user-agent kUserAgent will be sent
91 @return 0 if successful, else error
93 int get(const IPAddress& aServerAddress,
94 const char* aServerName,
97 const char* aUserAgent =NULL)
98 { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, kGet, aUserAgent); }
100 /** Connect to the server and start to send a GET request. This version connects
101 doesn't perform a DNS lookup and just connects to the given IP address.
102 @param aServerAddress IP address of the server to connect to
103 @param aServerName Name of the server being connected to. If NULL, the
104 "Host" header line won't be sent
105 @param aURLPath Url to request
106 @param aUserAgent User-Agent string to send. If NULL the default
107 user-agent kUserAgent will be sent
108 @return 0 if successful, else error
110 int get(const IPAddress& aServerAddress,
111 const char* aServerName,
112 const char* aURLPath,
113 const char* aUserAgent =NULL)
114 { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, kGet, aUserAgent); }
116 /** Connect to the server and start to send a POST request.
117 @param aServerName Name of the server being connected to. If NULL, the
118 "Host" header line won't be sent
119 @param aServerPort Port to connect to on the server
120 @param aURLPath Url to request
121 @param aUserAgent User-Agent string to send. If NULL the default
122 user-agent kUserAgent will be sent
123 @return 0 if successful, else error
125 int post(const char* aServerName,
126 uint16_t aServerPort,
127 const char* aURLPath,
128 const char* aUserAgent =NULL)
129 { return startRequest(aServerName, aServerPort, aURLPath, kPost, aUserAgent); }
131 /** Connect to the server and start to send a POST request.
132 @param aServerName Name of the server being connected to. If NULL, the
133 "Host" header line won't be sent
134 @param aURLPath Url to request
135 @param aUserAgent User-Agent string to send. If NULL the default
136 user-agent kUserAgent will be sent
137 @return 0 if successful, else error
139 int post(const char* aServerName,
140 const char* aURLPath,
141 const char* aUserAgent =NULL)
142 { return startRequest(aServerName, kHttpPort, aURLPath, kPost, aUserAgent); }
144 /** Connect to the server and start to send a POST request. This version connects
145 doesn't perform a DNS lookup and just connects to the given IP address.
146 @param aServerAddress IP address of the server to connect to
147 @param aServerName Name of the server being connected to. If NULL, the
148 "Host" header line won't be sent
149 @param aServerPort Port to connect to on the server
150 @param aURLPath Url to request
151 @param aUserAgent User-Agent string to send. If NULL the default
152 user-agent kUserAgent will be sent
153 @return 0 if successful, else error
155 int post(const IPAddress& aServerAddress,
156 const char* aServerName,
157 uint16_t aServerPort,
158 const char* aURLPath,
159 const char* aUserAgent =NULL)
160 { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, kPost, aUserAgent); }
162 /** Connect to the server and start to send a POST request. This version connects
163 doesn't perform a DNS lookup and just connects to the given IP address.
164 @param aServerAddress IP address of the server to connect to
165 @param aServerName Name of the server being connected to. If NULL, the
166 "Host" header line won't be sent
167 @param aURLPath Url to request
168 @param aUserAgent User-Agent string to send. If NULL the default
169 user-agent kUserAgent will be sent
170 @return 0 if successful, else error
172 int post(const IPAddress& aServerAddress,
173 const char* aServerName,
174 const char* aURLPath,
175 const char* aUserAgent =NULL)
176 { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, kPost, aUserAgent); }
178 /** Connect to the server and start to send a PUT request.
179 @param aServerName Name of the server being connected to. If NULL, the
180 "Host" header line won't be sent
181 @param aServerPort Port to connect to on the server
182 @param aURLPath Url to request
183 @param aUserAgent User-Agent string to send. If NULL the default
184 user-agent kUserAgent will be sent
185 @return 0 if successful, else error
187 int put(const char* aServerName,
188 uint16_t aServerPort,
189 const char* aURLPath,
190 const char* aUserAgent =NULL)
191 { return startRequest(aServerName, aServerPort, aURLPath, kPut, aUserAgent); }
193 /** Connect to the server and start to send a PUT request.
194 @param aServerName Name of the server being connected to. If NULL, the
195 "Host" header line won't be sent
196 @param aURLPath Url to request
197 @param aUserAgent User-Agent string to send. If NULL the default
198 user-agent kUserAgent will be sent
199 @return 0 if successful, else error
201 int put(const char* aServerName,
202 const char* aURLPath,
203 const char* aUserAgent =NULL)
204 { return startRequest(aServerName, kHttpPort, aURLPath, kPut, aUserAgent); }
206 /** Connect to the server and start to send a PUT request. This version connects
207 doesn't perform a DNS lookup and just connects to the given IP address.
208 @param aServerAddress IP address of the server to connect to
209 @param aServerName Name of the server being connected to. If NULL, the
210 "Host" header line won't be sent
211 @param aServerPort Port to connect to on the server
212 @param aURLPath Url to request
213 @param aUserAgent User-Agent string to send. If NULL the default
214 user-agent kUserAgent will be sent
215 @return 0 if successful, else error
217 int put(const IPAddress& aServerAddress,
218 const char* aServerName,
219 uint16_t aServerPort,
220 const char* aURLPath,
221 const char* aUserAgent =NULL)
222 { return startRequest(aServerAddress, aServerName, aServerPort, aURLPath, kPut, aUserAgent); }
224 /** Connect to the server and start to send a PUT request. This version connects
225 doesn't perform a DNS lookup and just connects to the given IP address.
226 @param aServerAddress IP address of the server to connect to
227 @param aServerName Name of the server being connected to. If NULL, the
228 "Host" header line won't be sent
229 @param aURLPath Url to request
230 @param aUserAgent User-Agent string to send. If NULL the default
231 user-agent kUserAgent will be sent
232 @return 0 if successful, else error
234 int put(const IPAddress& aServerAddress,
235 const char* aServerName,
236 const char* aURLPath,
237 const char* aUserAgent =NULL)
238 { return startRequest(aServerAddress, aServerName, kHttpPort, aURLPath, kPut, aUserAgent); }
240 /** Connect to the server and start to send the request.
241 @param aServerName Name of the server being connected to.
242 @param aServerPort Port to connect to on the server
243 @param aURLPath Url to request
244 @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
245 @param aUserAgent User-Agent string to send. If NULL the default
246 user-agent kUserAgent will be sent
247 @return 0 if successful, else error
249 int startRequest(const char* aServerName,
250 uint16_t aServerPort,
251 const char* aURLPath,
252 const char* aHttpMethod,
253 const char* aUserAgent);
255 /** Connect to the server and start to send the request.
256 @param aServerAddress IP address of the server to connect to.
257 @param aServerName Name of the server being connected to. If NULL, the
258 "Host" header line won't be sent
259 @param aServerPort Port to connect to on the server
260 @param aURLPath Url to request
261 @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
262 @param aUserAgent User-Agent string to send. If NULL the default
263 user-agent kUserAgent will be sent
264 @return 0 if successful, else error
266 int startRequest(const IPAddress& aServerAddress,
267 const char* aServerName,
268 uint16_t aServerPort,
269 const char* aURLPath,
270 const char* aHttpMethod,
271 const char* aUserAgent);
273 /** Send an additional header line. This can only be called in between the
274 calls to startRequest and finishRequest.
275 @param aHeader Header line to send, in its entirety (but without the
276 trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES"
278 void sendHeader(const char* aHeader);
280 /** Send an additional header line. This is an alternate form of
281 sendHeader() which takes the header name and content as separate strings.
282 The call will add the ": " to separate the header, so for example, to
283 send a XXXXXX header call sendHeader("XXXXX", "Something")
284 @param aHeaderName Type of header being sent
285 @param aHeaderValue Value for that header
287 void sendHeader(const char* aHeaderName, const char* aHeaderValue);
289 /** Send an additional header line. This is an alternate form of
290 sendHeader() which takes the header name and content separately but where
291 the value is provided as an integer.
292 The call will add the ": " to separate the header, so for example, to
293 send a XXXXXX header call sendHeader("XXXXX", 123)
294 @param aHeaderName Type of header being sent
295 @param aHeaderValue Value for that header
297 void sendHeader(const char* aHeaderName, const int aHeaderValue);
299 /** Send a basic authentication header. This will encode the given username
300 and password, and send them in suitable header line for doing Basic
302 @param aUser Username for the authorization
303 @param aPassword Password for the user aUser
305 void sendBasicAuth(const char* aUser, const char* aPassword);
307 /** Finish sending the HTTP request. This basically just sends the blank
308 line to signify the end of the request
310 void finishRequest();
312 /** Get the HTTP status code contained in the response.
313 For example, 200 for successful request, 404 for file not found, etc.
315 int responseStatusCode();
317 /** Read the next character of the response headers.
318 This functions in the same way as read() but to be used when reading
319 through the headers. Check whether or not the end of the headers has
320 been reached by calling endOfHeadersReached(), although after that point
321 this will still return data as read() would, but slightly less efficiently
322 @return The next character of the response headers
326 /** Skip any response headers to get to the body.
327 Use this if you don't want to do any special processing of the headers
328 returned in the response. You can also use it after you've found all of
329 the headers you're interested in, and just want to get on with processing
331 @return HTTP_SUCCESS if successful, else an error code
333 int skipResponseHeaders();
335 /** Test whether all of the response headers have been consumed.
336 @return true if we are now processing the response body, else false
338 bool endOfHeadersReached() { return (iState == eReadingBody); };
340 /** Test whether the end of the body has been reached.
341 Only works if the Content-Length header was returned by the server
342 @return true if we are now at the end of the body, else false
344 bool endOfBodyReached();
345 virtual bool endOfStream() { return endOfBodyReached(); };
346 virtual bool completed() { return endOfBodyReached(); };
348 /** Return the length of the body.
349 @return Length of the body, in bytes, or kNoContentLengthHeader if no
350 Content-Length header was returned by the server
352 int contentLength() { return iContentLength; };
354 // Inherited from Print
355 // Note: 1st call to these indicates the user is sending the body, so if need
356 // Note: be we should finish the header first
357 virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
358 virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
359 // Inherited from Stream
360 virtual int available() { return iClient->available(); };
361 /** Read the next byte from the server.
362 @return Byte read or -1 if there are no bytes available.
365 virtual int read(uint8_t *buf, size_t size);
366 virtual int peek() { return iClient->peek(); };
367 virtual void flush() { return iClient->flush(); };
369 // Inherited from Client
370 virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); };
371 virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); };
373 virtual uint8_t connected() { iClient->connected(); };
374 virtual operator bool() { return bool(iClient); };
376 /** Reset internal state data back to the "just initialised" state
380 /** Send the first part of the request and the initial headers.
381 @param aServerName Name of the server being connected to. If NULL, the
382 "Host" header line won't be sent
383 @param aServerIP IP address of the server (only used if we're going through a
384 proxy and aServerName is NULL
385 @param aServerPort Port of the server being connected to.
386 @param aURLPath Url to request
387 @param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
388 @param aUserAgent User-Agent string to send. If NULL the default
389 user-agent kUserAgent will be sent
390 @return 0 if successful, else error
392 int sendInitialHeaders(const char* aServerName,
395 const char* aURLPath,
396 const char* aHttpMethod,
397 const char* aUserAgent);
399 /* Let the server know that we've reached the end of the headers
401 void finishHeaders();
403 // Number of milliseconds that we wait each time there isn't any data
404 // available to be read (during status code and header processing)
405 static const int kHttpWaitForDataDelay = 1000;
406 // Number of milliseconds that we'll wait in total without receiveing any
407 // data before returning HTTP_ERROR_TIMED_OUT (during status code and header
409 static const int kHttpResponseTimeout = 30*1000;
410 static const char* kContentLengthPrefix;
417 eReadingContentLength,
419 eLineStartingCRFound,
422 // Ethernet client we're using
424 // Current state of the finite-state-machine
426 // Stores the status code for the response, once known
428 // Stores the value of the Content-Length header, if present
430 // How many bytes of the response body have been read by the user
431 int iBodyLengthConsumed;
432 // How far through a Content-Length header prefix we are
433 const char* iContentLengthPtr;
434 // Address of the proxy to use, if we're using one
435 IPAddress iProxyAddress;