Free (GPLv2) TCP/IP stack developed by TASS Belgium
Fork of PicoTCP by
modules/pico_http_server.c@3:b4047e8a0123, 2013-05-24 (annotated)
- Committer:
- daniele
- Date:
- Fri May 24 15:25:25 2013 +0000
- Revision:
- 3:b4047e8a0123
Updated from main repo + fixed Mutexes;
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
daniele | 3:b4047e8a0123 | 1 | /********************************************************************* |
daniele | 3:b4047e8a0123 | 2 | PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved. |
daniele | 3:b4047e8a0123 | 3 | See LICENSE and COPYING for usage. |
daniele | 3:b4047e8a0123 | 4 | |
daniele | 3:b4047e8a0123 | 5 | Author: Andrei Carp <andrei.carp@tass.be> |
daniele | 3:b4047e8a0123 | 6 | *********************************************************************/ |
daniele | 3:b4047e8a0123 | 7 | |
daniele | 3:b4047e8a0123 | 8 | #include "pico_stack.h" |
daniele | 3:b4047e8a0123 | 9 | #include "pico_http_server.h" |
daniele | 3:b4047e8a0123 | 10 | #include "pico_tcp.h" |
daniele | 3:b4047e8a0123 | 11 | #include "pico_tree.h" |
daniele | 3:b4047e8a0123 | 12 | #include "pico_socket.h" |
daniele | 3:b4047e8a0123 | 13 | |
daniele | 3:b4047e8a0123 | 14 | #ifdef PICO_SUPPORT_HTTP_SERVER |
daniele | 3:b4047e8a0123 | 15 | |
daniele | 3:b4047e8a0123 | 16 | #define BACKLOG 10 |
daniele | 3:b4047e8a0123 | 17 | |
daniele | 3:b4047e8a0123 | 18 | #define HTTP_SERVER_CLOSED 0 |
daniele | 3:b4047e8a0123 | 19 | #define HTTP_SERVER_LISTEN 1 |
daniele | 3:b4047e8a0123 | 20 | |
daniele | 3:b4047e8a0123 | 21 | #define HTTP_HEADER_MAX_LINE 256u |
daniele | 3:b4047e8a0123 | 22 | |
daniele | 3:b4047e8a0123 | 23 | #define consumeChar(c) (pico_socket_read(client->sck,&c,1u)) |
daniele | 3:b4047e8a0123 | 24 | |
daniele | 3:b4047e8a0123 | 25 | static char returnOkHeader[] = |
daniele | 3:b4047e8a0123 | 26 | "HTTP/1.1 200 OK\r\n\ |
daniele | 3:b4047e8a0123 | 27 | Host: localhost\r\n\ |
daniele | 3:b4047e8a0123 | 28 | Transfer-Encoding: chunked\r\n\ |
daniele | 3:b4047e8a0123 | 29 | Connection: close\r\n\ |
daniele | 3:b4047e8a0123 | 30 | \r\n"; |
daniele | 3:b4047e8a0123 | 31 | |
daniele | 3:b4047e8a0123 | 32 | static char returnFailHeader[] = |
daniele | 3:b4047e8a0123 | 33 | "HTTP/1.1 404 Not Found\r\n\ |
daniele | 3:b4047e8a0123 | 34 | Host: localhost\r\n\ |
daniele | 3:b4047e8a0123 | 35 | Connection: close\r\n\ |
daniele | 3:b4047e8a0123 | 36 | \r\n\ |
daniele | 3:b4047e8a0123 | 37 | <html><body>The resource you requested cannot be found !</body></html>"; |
daniele | 3:b4047e8a0123 | 38 | |
daniele | 3:b4047e8a0123 | 39 | static char errorHeader[] = |
daniele | 3:b4047e8a0123 | 40 | "HTTP/1.1 400 Bad Request\r\n\ |
daniele | 3:b4047e8a0123 | 41 | Host: localhost\r\n\ |
daniele | 3:b4047e8a0123 | 42 | Connection: close\r\n\ |
daniele | 3:b4047e8a0123 | 43 | \r\n\ |
daniele | 3:b4047e8a0123 | 44 | <html><body>There was a problem with your request !</body></html>"; |
daniele | 3:b4047e8a0123 | 45 | |
daniele | 3:b4047e8a0123 | 46 | struct httpServer |
daniele | 3:b4047e8a0123 | 47 | { |
daniele | 3:b4047e8a0123 | 48 | uint16_t state; |
daniele | 3:b4047e8a0123 | 49 | struct pico_socket * sck; |
daniele | 3:b4047e8a0123 | 50 | uint16_t port; |
daniele | 3:b4047e8a0123 | 51 | void (*wakeup)(uint16_t ev, uint16_t param); |
daniele | 3:b4047e8a0123 | 52 | uint8_t accepted; |
daniele | 3:b4047e8a0123 | 53 | }; |
daniele | 3:b4047e8a0123 | 54 | |
daniele | 3:b4047e8a0123 | 55 | struct httpClient |
daniele | 3:b4047e8a0123 | 56 | { |
daniele | 3:b4047e8a0123 | 57 | uint16_t connectionID; |
daniele | 3:b4047e8a0123 | 58 | struct pico_socket * sck; |
daniele | 3:b4047e8a0123 | 59 | void * buffer; |
daniele | 3:b4047e8a0123 | 60 | uint16_t bufferSize; |
daniele | 3:b4047e8a0123 | 61 | uint16_t bufferSent; |
daniele | 3:b4047e8a0123 | 62 | char * resource; |
daniele | 3:b4047e8a0123 | 63 | uint16_t state; |
daniele | 3:b4047e8a0123 | 64 | }; |
daniele | 3:b4047e8a0123 | 65 | |
daniele | 3:b4047e8a0123 | 66 | /* Local states for clients */ |
daniele | 3:b4047e8a0123 | 67 | #define HTTP_WAIT_HDR 0 |
daniele | 3:b4047e8a0123 | 68 | #define HTTP_WAIT_EOF_HDR 1 |
daniele | 3:b4047e8a0123 | 69 | #define HTTP_EOF_HDR 2 |
daniele | 3:b4047e8a0123 | 70 | #define HTTP_WAIT_RESPONSE 3 |
daniele | 3:b4047e8a0123 | 71 | #define HTTP_WAIT_DATA 4 |
daniele | 3:b4047e8a0123 | 72 | #define HTTP_SENDING_DATA 5 |
daniele | 3:b4047e8a0123 | 73 | #define HTTP_ERROR 6 |
daniele | 3:b4047e8a0123 | 74 | #define HTTP_CLOSED 7 |
daniele | 3:b4047e8a0123 | 75 | |
daniele | 3:b4047e8a0123 | 76 | static struct httpServer server = {}; |
daniele | 3:b4047e8a0123 | 77 | |
daniele | 3:b4047e8a0123 | 78 | /* |
daniele | 3:b4047e8a0123 | 79 | * Private functions |
daniele | 3:b4047e8a0123 | 80 | */ |
daniele | 3:b4047e8a0123 | 81 | static int parseRequest(struct httpClient * client); |
daniele | 3:b4047e8a0123 | 82 | static int readRemainingHeader(struct httpClient * client); |
daniele | 3:b4047e8a0123 | 83 | static void sendData(struct httpClient * client); |
daniele | 3:b4047e8a0123 | 84 | static inline int readData(struct httpClient * client); // used only in a place |
daniele | 3:b4047e8a0123 | 85 | static inline struct httpClient * findClient(uint16_t conn); |
daniele | 3:b4047e8a0123 | 86 | |
daniele | 3:b4047e8a0123 | 87 | static int compareClients(void * ka, void * kb) |
daniele | 3:b4047e8a0123 | 88 | { |
daniele | 3:b4047e8a0123 | 89 | return ((struct httpClient *)ka)->connectionID - ((struct httpClient *)kb)->connectionID; |
daniele | 3:b4047e8a0123 | 90 | } |
daniele | 3:b4047e8a0123 | 91 | |
daniele | 3:b4047e8a0123 | 92 | PICO_TREE_DECLARE(pico_http_clients,compareClients); |
daniele | 3:b4047e8a0123 | 93 | |
daniele | 3:b4047e8a0123 | 94 | void httpServerCbk(uint16_t ev, struct pico_socket *s) |
daniele | 3:b4047e8a0123 | 95 | { |
daniele | 3:b4047e8a0123 | 96 | struct pico_tree_node * index; |
daniele | 3:b4047e8a0123 | 97 | struct httpClient * client = NULL; |
daniele | 3:b4047e8a0123 | 98 | uint8_t serverEvent = FALSE; |
daniele | 3:b4047e8a0123 | 99 | |
daniele | 3:b4047e8a0123 | 100 | // determine the client for the socket |
daniele | 3:b4047e8a0123 | 101 | if( s == server.sck) |
daniele | 3:b4047e8a0123 | 102 | { |
daniele | 3:b4047e8a0123 | 103 | serverEvent = TRUE; |
daniele | 3:b4047e8a0123 | 104 | } |
daniele | 3:b4047e8a0123 | 105 | else |
daniele | 3:b4047e8a0123 | 106 | { |
daniele | 3:b4047e8a0123 | 107 | pico_tree_foreach(index,&pico_http_clients) |
daniele | 3:b4047e8a0123 | 108 | { |
daniele | 3:b4047e8a0123 | 109 | client = index->keyValue; |
daniele | 3:b4047e8a0123 | 110 | if(client->sck == s) break; |
daniele | 3:b4047e8a0123 | 111 | client = NULL; |
daniele | 3:b4047e8a0123 | 112 | } |
daniele | 3:b4047e8a0123 | 113 | } |
daniele | 3:b4047e8a0123 | 114 | |
daniele | 3:b4047e8a0123 | 115 | if(!client && !serverEvent) |
daniele | 3:b4047e8a0123 | 116 | { |
daniele | 3:b4047e8a0123 | 117 | return; |
daniele | 3:b4047e8a0123 | 118 | } |
daniele | 3:b4047e8a0123 | 119 | |
daniele | 3:b4047e8a0123 | 120 | if (ev & PICO_SOCK_EV_RD) |
daniele | 3:b4047e8a0123 | 121 | { |
daniele | 3:b4047e8a0123 | 122 | |
daniele | 3:b4047e8a0123 | 123 | if(readData(client) == HTTP_RETURN_ERROR) |
daniele | 3:b4047e8a0123 | 124 | { |
daniele | 3:b4047e8a0123 | 125 | // send out error |
daniele | 3:b4047e8a0123 | 126 | client->state = HTTP_ERROR; |
daniele | 3:b4047e8a0123 | 127 | pico_socket_write(client->sck,errorHeader,sizeof(errorHeader)-1); |
daniele | 3:b4047e8a0123 | 128 | server.wakeup(EV_HTTP_ERROR,client->connectionID); |
daniele | 3:b4047e8a0123 | 129 | } |
daniele | 3:b4047e8a0123 | 130 | } |
daniele | 3:b4047e8a0123 | 131 | |
daniele | 3:b4047e8a0123 | 132 | if(ev & PICO_SOCK_EV_WR) |
daniele | 3:b4047e8a0123 | 133 | { |
daniele | 3:b4047e8a0123 | 134 | if(client->state == HTTP_SENDING_DATA) |
daniele | 3:b4047e8a0123 | 135 | { |
daniele | 3:b4047e8a0123 | 136 | sendData(client); |
daniele | 3:b4047e8a0123 | 137 | } |
daniele | 3:b4047e8a0123 | 138 | } |
daniele | 3:b4047e8a0123 | 139 | |
daniele | 3:b4047e8a0123 | 140 | if(ev & PICO_SOCK_EV_CONN) |
daniele | 3:b4047e8a0123 | 141 | { |
daniele | 3:b4047e8a0123 | 142 | server.accepted = FALSE; |
daniele | 3:b4047e8a0123 | 143 | server.wakeup(EV_HTTP_CON,HTTP_SERVER_ID); |
daniele | 3:b4047e8a0123 | 144 | if(!server.accepted) |
daniele | 3:b4047e8a0123 | 145 | { |
daniele | 3:b4047e8a0123 | 146 | pico_socket_close(s); // reject socket |
daniele | 3:b4047e8a0123 | 147 | } |
daniele | 3:b4047e8a0123 | 148 | } |
daniele | 3:b4047e8a0123 | 149 | |
daniele | 3:b4047e8a0123 | 150 | if( (ev & PICO_SOCK_EV_CLOSE) || (ev & PICO_SOCK_EV_FIN) ) |
daniele | 3:b4047e8a0123 | 151 | { |
daniele | 3:b4047e8a0123 | 152 | server.wakeup(EV_HTTP_CLOSE,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); |
daniele | 3:b4047e8a0123 | 153 | } |
daniele | 3:b4047e8a0123 | 154 | |
daniele | 3:b4047e8a0123 | 155 | if(ev & PICO_SOCK_EV_ERR) |
daniele | 3:b4047e8a0123 | 156 | { |
daniele | 3:b4047e8a0123 | 157 | server.wakeup(EV_HTTP_ERROR,(serverEvent ? HTTP_SERVER_ID : client->connectionID)); |
daniele | 3:b4047e8a0123 | 158 | } |
daniele | 3:b4047e8a0123 | 159 | } |
daniele | 3:b4047e8a0123 | 160 | |
daniele | 3:b4047e8a0123 | 161 | /* |
daniele | 3:b4047e8a0123 | 162 | * API for starting the server. If 0 is passed as a port, the port 80 |
daniele | 3:b4047e8a0123 | 163 | * will be used. |
daniele | 3:b4047e8a0123 | 164 | */ |
daniele | 3:b4047e8a0123 | 165 | int pico_http_server_start(uint16_t port, void (*wakeup)(uint16_t ev, uint16_t conn)) |
daniele | 3:b4047e8a0123 | 166 | { |
daniele | 3:b4047e8a0123 | 167 | struct pico_ip4 anything = {}; |
daniele | 3:b4047e8a0123 | 168 | |
daniele | 3:b4047e8a0123 | 169 | server.port = port ? short_be(port) : short_be(80u); |
daniele | 3:b4047e8a0123 | 170 | |
daniele | 3:b4047e8a0123 | 171 | if(!wakeup) |
daniele | 3:b4047e8a0123 | 172 | { |
daniele | 3:b4047e8a0123 | 173 | pico_err = PICO_ERR_EINVAL; |
daniele | 3:b4047e8a0123 | 174 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 175 | } |
daniele | 3:b4047e8a0123 | 176 | |
daniele | 3:b4047e8a0123 | 177 | server.sck = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &httpServerCbk); |
daniele | 3:b4047e8a0123 | 178 | |
daniele | 3:b4047e8a0123 | 179 | if(!server.sck) |
daniele | 3:b4047e8a0123 | 180 | { |
daniele | 3:b4047e8a0123 | 181 | pico_err = PICO_ERR_EFAULT; |
daniele | 3:b4047e8a0123 | 182 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 183 | } |
daniele | 3:b4047e8a0123 | 184 | |
daniele | 3:b4047e8a0123 | 185 | if(pico_socket_bind(server.sck , &anything, &server.port)!=0) |
daniele | 3:b4047e8a0123 | 186 | { |
daniele | 3:b4047e8a0123 | 187 | pico_err = PICO_ERR_EADDRNOTAVAIL; |
daniele | 3:b4047e8a0123 | 188 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 189 | } |
daniele | 3:b4047e8a0123 | 190 | |
daniele | 3:b4047e8a0123 | 191 | if (pico_socket_listen(server.sck, BACKLOG) != 0) |
daniele | 3:b4047e8a0123 | 192 | { |
daniele | 3:b4047e8a0123 | 193 | pico_err = PICO_ERR_EADDRINUSE; |
daniele | 3:b4047e8a0123 | 194 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 195 | } |
daniele | 3:b4047e8a0123 | 196 | server.wakeup = wakeup; |
daniele | 3:b4047e8a0123 | 197 | server.state = HTTP_SERVER_LISTEN; |
daniele | 3:b4047e8a0123 | 198 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 199 | } |
daniele | 3:b4047e8a0123 | 200 | |
daniele | 3:b4047e8a0123 | 201 | /* |
daniele | 3:b4047e8a0123 | 202 | * API for accepting new connections. This function should be |
daniele | 3:b4047e8a0123 | 203 | * called when the event EV_HTTP_CON is triggered, if not called |
daniele | 3:b4047e8a0123 | 204 | * when noticed the connection will be considered rejected and the |
daniele | 3:b4047e8a0123 | 205 | * socket will be dropped. |
daniele | 3:b4047e8a0123 | 206 | * |
daniele | 3:b4047e8a0123 | 207 | * Returns the ID of the new connection or a negative value if error. |
daniele | 3:b4047e8a0123 | 208 | */ |
daniele | 3:b4047e8a0123 | 209 | int pico_http_server_accept(void) |
daniele | 3:b4047e8a0123 | 210 | { |
daniele | 3:b4047e8a0123 | 211 | struct pico_ip4 orig; |
daniele | 3:b4047e8a0123 | 212 | struct httpClient * client; |
daniele | 3:b4047e8a0123 | 213 | uint16_t port; |
daniele | 3:b4047e8a0123 | 214 | |
daniele | 3:b4047e8a0123 | 215 | client = pico_zalloc(sizeof(struct httpClient)); |
daniele | 3:b4047e8a0123 | 216 | if(!client) |
daniele | 3:b4047e8a0123 | 217 | { |
daniele | 3:b4047e8a0123 | 218 | pico_err = PICO_ERR_ENOMEM; |
daniele | 3:b4047e8a0123 | 219 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 220 | } |
daniele | 3:b4047e8a0123 | 221 | |
daniele | 3:b4047e8a0123 | 222 | client->sck = pico_socket_accept(server.sck,&orig,&port); |
daniele | 3:b4047e8a0123 | 223 | |
daniele | 3:b4047e8a0123 | 224 | if(!client->sck) |
daniele | 3:b4047e8a0123 | 225 | { |
daniele | 3:b4047e8a0123 | 226 | pico_err = PICO_ERR_ENOMEM; |
daniele | 3:b4047e8a0123 | 227 | pico_free(client); |
daniele | 3:b4047e8a0123 | 228 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 229 | } |
daniele | 3:b4047e8a0123 | 230 | |
daniele | 3:b4047e8a0123 | 231 | server.accepted = TRUE; |
daniele | 3:b4047e8a0123 | 232 | // buffer used for async sending |
daniele | 3:b4047e8a0123 | 233 | client->state = HTTP_WAIT_HDR; |
daniele | 3:b4047e8a0123 | 234 | client->buffer = NULL; |
daniele | 3:b4047e8a0123 | 235 | client->bufferSize = 0; |
daniele | 3:b4047e8a0123 | 236 | client->connectionID = pico_rand() & 0x7FFF; |
daniele | 3:b4047e8a0123 | 237 | |
daniele | 3:b4047e8a0123 | 238 | //add element to the tree, if duplicate because the rand |
daniele | 3:b4047e8a0123 | 239 | //regenerate |
daniele | 3:b4047e8a0123 | 240 | while(pico_tree_insert(&pico_http_clients,client)!=NULL) |
daniele | 3:b4047e8a0123 | 241 | client->connectionID = pico_rand() & 0x7FFF; |
daniele | 3:b4047e8a0123 | 242 | |
daniele | 3:b4047e8a0123 | 243 | return client->connectionID; |
daniele | 3:b4047e8a0123 | 244 | } |
daniele | 3:b4047e8a0123 | 245 | |
daniele | 3:b4047e8a0123 | 246 | /* |
daniele | 3:b4047e8a0123 | 247 | * Function used for getting the resource asked by the |
daniele | 3:b4047e8a0123 | 248 | * client. It is useful after the request header (EV_HTTP_REQ) |
daniele | 3:b4047e8a0123 | 249 | * from client was received, otherwise NULL is returned. |
daniele | 3:b4047e8a0123 | 250 | */ |
daniele | 3:b4047e8a0123 | 251 | char * pico_http_getResource(uint16_t conn) |
daniele | 3:b4047e8a0123 | 252 | { |
daniele | 3:b4047e8a0123 | 253 | struct httpClient * client = findClient(conn); |
daniele | 3:b4047e8a0123 | 254 | |
daniele | 3:b4047e8a0123 | 255 | if(!client) |
daniele | 3:b4047e8a0123 | 256 | return NULL; |
daniele | 3:b4047e8a0123 | 257 | else |
daniele | 3:b4047e8a0123 | 258 | return client->resource; |
daniele | 3:b4047e8a0123 | 259 | } |
daniele | 3:b4047e8a0123 | 260 | |
daniele | 3:b4047e8a0123 | 261 | /* |
daniele | 3:b4047e8a0123 | 262 | * After the resource was asked by the client (EV_HTTP_REQ) |
daniele | 3:b4047e8a0123 | 263 | * before doing anything else, the server has to let know |
daniele | 3:b4047e8a0123 | 264 | * the client if the resource can be provided or not. |
daniele | 3:b4047e8a0123 | 265 | * |
daniele | 3:b4047e8a0123 | 266 | * This is controlled via the code parameter which can |
daniele | 3:b4047e8a0123 | 267 | * have two values : |
daniele | 3:b4047e8a0123 | 268 | * |
daniele | 3:b4047e8a0123 | 269 | * HTTP_RESOURCE_FOUND or HTTP_RESOURCE_NOT_FOUND |
daniele | 3:b4047e8a0123 | 270 | * |
daniele | 3:b4047e8a0123 | 271 | * If a resource is reported not found the 404 header will be sent and the connection |
daniele | 3:b4047e8a0123 | 272 | * will be closed , otherwise the 200 header is sent and the user should |
daniele | 3:b4047e8a0123 | 273 | * immediately submit data. |
daniele | 3:b4047e8a0123 | 274 | * |
daniele | 3:b4047e8a0123 | 275 | */ |
daniele | 3:b4047e8a0123 | 276 | int pico_http_respond(uint16_t conn, uint16_t code) |
daniele | 3:b4047e8a0123 | 277 | { |
daniele | 3:b4047e8a0123 | 278 | struct httpClient * client = findClient(conn); |
daniele | 3:b4047e8a0123 | 279 | |
daniele | 3:b4047e8a0123 | 280 | if(!client) |
daniele | 3:b4047e8a0123 | 281 | { |
daniele | 3:b4047e8a0123 | 282 | dbg("Client not found !\n"); |
daniele | 3:b4047e8a0123 | 283 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 284 | } |
daniele | 3:b4047e8a0123 | 285 | |
daniele | 3:b4047e8a0123 | 286 | if(client->state == HTTP_WAIT_RESPONSE) |
daniele | 3:b4047e8a0123 | 287 | { |
daniele | 3:b4047e8a0123 | 288 | if(code == HTTP_RESOURCE_FOUND) |
daniele | 3:b4047e8a0123 | 289 | { |
daniele | 3:b4047e8a0123 | 290 | client->state = HTTP_WAIT_DATA; |
daniele | 3:b4047e8a0123 | 291 | return pico_socket_write(client->sck,returnOkHeader,sizeof(returnOkHeader)-1);//remove \0 |
daniele | 3:b4047e8a0123 | 292 | } |
daniele | 3:b4047e8a0123 | 293 | else |
daniele | 3:b4047e8a0123 | 294 | { |
daniele | 3:b4047e8a0123 | 295 | int length; |
daniele | 3:b4047e8a0123 | 296 | |
daniele | 3:b4047e8a0123 | 297 | length = pico_socket_write(client->sck,returnFailHeader,sizeof(returnFailHeader)-1);//remove \0 |
daniele | 3:b4047e8a0123 | 298 | pico_socket_close(client->sck); |
daniele | 3:b4047e8a0123 | 299 | client->state = HTTP_CLOSED; |
daniele | 3:b4047e8a0123 | 300 | return length; |
daniele | 3:b4047e8a0123 | 301 | |
daniele | 3:b4047e8a0123 | 302 | } |
daniele | 3:b4047e8a0123 | 303 | } |
daniele | 3:b4047e8a0123 | 304 | else |
daniele | 3:b4047e8a0123 | 305 | { |
daniele | 3:b4047e8a0123 | 306 | dbg("Bad state for the client \n"); |
daniele | 3:b4047e8a0123 | 307 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 308 | } |
daniele | 3:b4047e8a0123 | 309 | |
daniele | 3:b4047e8a0123 | 310 | } |
daniele | 3:b4047e8a0123 | 311 | |
daniele | 3:b4047e8a0123 | 312 | /* |
daniele | 3:b4047e8a0123 | 313 | * API used to submit data to the client. |
daniele | 3:b4047e8a0123 | 314 | * Server sends data only using Transfer-Encoding: chunked. |
daniele | 3:b4047e8a0123 | 315 | * |
daniele | 3:b4047e8a0123 | 316 | * With this function the user will submit a data chunk to |
daniele | 3:b4047e8a0123 | 317 | * be sent. |
daniele | 3:b4047e8a0123 | 318 | * The function will send the chunk size in hex and the rest will |
daniele | 3:b4047e8a0123 | 319 | * be sent using WR event from sockets. |
daniele | 3:b4047e8a0123 | 320 | * After each transmision EV_HTTP_PROGRESS is called and at the |
daniele | 3:b4047e8a0123 | 321 | * end of the chunk EV_HTTP_SENT is called. |
daniele | 3:b4047e8a0123 | 322 | * |
daniele | 3:b4047e8a0123 | 323 | * To let the client know this is the last chunk, the user |
daniele | 3:b4047e8a0123 | 324 | * should pass a NULL buffer. |
daniele | 3:b4047e8a0123 | 325 | */ |
daniele | 3:b4047e8a0123 | 326 | int pico_http_submitData(uint16_t conn, void * buffer, int len) |
daniele | 3:b4047e8a0123 | 327 | { |
daniele | 3:b4047e8a0123 | 328 | |
daniele | 3:b4047e8a0123 | 329 | struct httpClient * client = findClient(conn); |
daniele | 3:b4047e8a0123 | 330 | char chunkStr[10]; |
daniele | 3:b4047e8a0123 | 331 | int chunkCount; |
daniele | 3:b4047e8a0123 | 332 | |
daniele | 3:b4047e8a0123 | 333 | if(client->state != HTTP_WAIT_DATA) |
daniele | 3:b4047e8a0123 | 334 | { |
daniele | 3:b4047e8a0123 | 335 | dbg("Client is in a different state than accepted\n"); |
daniele | 3:b4047e8a0123 | 336 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 337 | } |
daniele | 3:b4047e8a0123 | 338 | |
daniele | 3:b4047e8a0123 | 339 | if(client->buffer) |
daniele | 3:b4047e8a0123 | 340 | { |
daniele | 3:b4047e8a0123 | 341 | dbg("Already a buffer submited\n"); |
daniele | 3:b4047e8a0123 | 342 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 343 | } |
daniele | 3:b4047e8a0123 | 344 | |
daniele | 3:b4047e8a0123 | 345 | if(!client) |
daniele | 3:b4047e8a0123 | 346 | { |
daniele | 3:b4047e8a0123 | 347 | dbg("Wrong connection ID\n"); |
daniele | 3:b4047e8a0123 | 348 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 349 | } |
daniele | 3:b4047e8a0123 | 350 | |
daniele | 3:b4047e8a0123 | 351 | if(!buffer) |
daniele | 3:b4047e8a0123 | 352 | { |
daniele | 3:b4047e8a0123 | 353 | len = 0; |
daniele | 3:b4047e8a0123 | 354 | } |
daniele | 3:b4047e8a0123 | 355 | |
daniele | 3:b4047e8a0123 | 356 | if(len > 0) |
daniele | 3:b4047e8a0123 | 357 | { |
daniele | 3:b4047e8a0123 | 358 | client->buffer = pico_zalloc(len); |
daniele | 3:b4047e8a0123 | 359 | if(!client->buffer) |
daniele | 3:b4047e8a0123 | 360 | { |
daniele | 3:b4047e8a0123 | 361 | pico_err = PICO_ERR_ENOMEM; |
daniele | 3:b4047e8a0123 | 362 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 363 | } |
daniele | 3:b4047e8a0123 | 364 | // taking over the buffer |
daniele | 3:b4047e8a0123 | 365 | memcpy(client->buffer,buffer,len); |
daniele | 3:b4047e8a0123 | 366 | } |
daniele | 3:b4047e8a0123 | 367 | else |
daniele | 3:b4047e8a0123 | 368 | client->buffer = NULL; |
daniele | 3:b4047e8a0123 | 369 | |
daniele | 3:b4047e8a0123 | 370 | |
daniele | 3:b4047e8a0123 | 371 | client->bufferSize = len; |
daniele | 3:b4047e8a0123 | 372 | client->bufferSent = 0; |
daniele | 3:b4047e8a0123 | 373 | |
daniele | 3:b4047e8a0123 | 374 | // create the chunk size and send it |
daniele | 3:b4047e8a0123 | 375 | if(len > 0) |
daniele | 3:b4047e8a0123 | 376 | { |
daniele | 3:b4047e8a0123 | 377 | client->state = HTTP_SENDING_DATA; |
daniele | 3:b4047e8a0123 | 378 | chunkCount = pico_itoaHex(client->bufferSize,chunkStr); |
daniele | 3:b4047e8a0123 | 379 | chunkStr[chunkCount++] = '\r'; |
daniele | 3:b4047e8a0123 | 380 | chunkStr[chunkCount++] = '\n'; |
daniele | 3:b4047e8a0123 | 381 | pico_socket_write(client->sck,chunkStr,chunkCount); |
daniele | 3:b4047e8a0123 | 382 | } |
daniele | 3:b4047e8a0123 | 383 | else if(len == 0) |
daniele | 3:b4047e8a0123 | 384 | { |
daniele | 3:b4047e8a0123 | 385 | dbg("->\n"); |
daniele | 3:b4047e8a0123 | 386 | // end of transmision |
daniele | 3:b4047e8a0123 | 387 | pico_socket_write(client->sck,"0\r\n\r\n",5u); |
daniele | 3:b4047e8a0123 | 388 | // nothing left, close the client |
daniele | 3:b4047e8a0123 | 389 | pico_socket_close(client->sck); |
daniele | 3:b4047e8a0123 | 390 | client->state = HTTP_CLOSED; |
daniele | 3:b4047e8a0123 | 391 | } |
daniele | 3:b4047e8a0123 | 392 | |
daniele | 3:b4047e8a0123 | 393 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 394 | } |
daniele | 3:b4047e8a0123 | 395 | |
daniele | 3:b4047e8a0123 | 396 | /* |
daniele | 3:b4047e8a0123 | 397 | * When EV_HTTP_PROGRESS is triggered you can use this |
daniele | 3:b4047e8a0123 | 398 | * function to check the state of the chunk. |
daniele | 3:b4047e8a0123 | 399 | */ |
daniele | 3:b4047e8a0123 | 400 | |
daniele | 3:b4047e8a0123 | 401 | int pico_http_getProgress(uint16_t conn, uint16_t * sent, uint16_t *total) |
daniele | 3:b4047e8a0123 | 402 | { |
daniele | 3:b4047e8a0123 | 403 | struct httpClient * client = findClient(conn); |
daniele | 3:b4047e8a0123 | 404 | |
daniele | 3:b4047e8a0123 | 405 | if(!client) |
daniele | 3:b4047e8a0123 | 406 | { |
daniele | 3:b4047e8a0123 | 407 | dbg("Wrong connection id !\n"); |
daniele | 3:b4047e8a0123 | 408 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 409 | } |
daniele | 3:b4047e8a0123 | 410 | |
daniele | 3:b4047e8a0123 | 411 | *sent = client->bufferSent; |
daniele | 3:b4047e8a0123 | 412 | *total = client->bufferSize; |
daniele | 3:b4047e8a0123 | 413 | |
daniele | 3:b4047e8a0123 | 414 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 415 | } |
daniele | 3:b4047e8a0123 | 416 | |
daniele | 3:b4047e8a0123 | 417 | /* |
daniele | 3:b4047e8a0123 | 418 | * This API can be used to close either a client |
daniele | 3:b4047e8a0123 | 419 | * or the server ( if you pass HTTP_SERVER_ID as a connection ID). |
daniele | 3:b4047e8a0123 | 420 | */ |
daniele | 3:b4047e8a0123 | 421 | int pico_http_close(uint16_t conn) |
daniele | 3:b4047e8a0123 | 422 | { |
daniele | 3:b4047e8a0123 | 423 | // close the server |
daniele | 3:b4047e8a0123 | 424 | if(conn == HTTP_SERVER_ID) |
daniele | 3:b4047e8a0123 | 425 | { |
daniele | 3:b4047e8a0123 | 426 | if(server.state == HTTP_SERVER_LISTEN) |
daniele | 3:b4047e8a0123 | 427 | { |
daniele | 3:b4047e8a0123 | 428 | struct pico_tree_node * index, * tmp; |
daniele | 3:b4047e8a0123 | 429 | // close the server |
daniele | 3:b4047e8a0123 | 430 | pico_socket_close(server.sck); |
daniele | 3:b4047e8a0123 | 431 | server.sck = NULL; |
daniele | 3:b4047e8a0123 | 432 | |
daniele | 3:b4047e8a0123 | 433 | // destroy the tree |
daniele | 3:b4047e8a0123 | 434 | pico_tree_foreach_safe(index,&pico_http_clients,tmp) |
daniele | 3:b4047e8a0123 | 435 | { |
daniele | 3:b4047e8a0123 | 436 | struct httpClient * client = index->keyValue; |
daniele | 3:b4047e8a0123 | 437 | |
daniele | 3:b4047e8a0123 | 438 | if(client->resource) |
daniele | 3:b4047e8a0123 | 439 | pico_free(client->resource); |
daniele | 3:b4047e8a0123 | 440 | |
daniele | 3:b4047e8a0123 | 441 | pico_socket_close(client->sck); |
daniele | 3:b4047e8a0123 | 442 | pico_tree_delete(&pico_http_clients,client); |
daniele | 3:b4047e8a0123 | 443 | } |
daniele | 3:b4047e8a0123 | 444 | |
daniele | 3:b4047e8a0123 | 445 | server.state = HTTP_SERVER_CLOSED; |
daniele | 3:b4047e8a0123 | 446 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 447 | } |
daniele | 3:b4047e8a0123 | 448 | else // nothing to close |
daniele | 3:b4047e8a0123 | 449 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 450 | } // close a connection in this case |
daniele | 3:b4047e8a0123 | 451 | else |
daniele | 3:b4047e8a0123 | 452 | { |
daniele | 3:b4047e8a0123 | 453 | |
daniele | 3:b4047e8a0123 | 454 | struct httpClient * client = findClient(conn); |
daniele | 3:b4047e8a0123 | 455 | |
daniele | 3:b4047e8a0123 | 456 | if(!client) |
daniele | 3:b4047e8a0123 | 457 | { |
daniele | 3:b4047e8a0123 | 458 | dbg("Client not found..\n"); |
daniele | 3:b4047e8a0123 | 459 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 460 | } |
daniele | 3:b4047e8a0123 | 461 | |
daniele | 3:b4047e8a0123 | 462 | pico_tree_delete(&pico_http_clients,client); |
daniele | 3:b4047e8a0123 | 463 | |
daniele | 3:b4047e8a0123 | 464 | if(client->resource) |
daniele | 3:b4047e8a0123 | 465 | pico_free(client->resource); |
daniele | 3:b4047e8a0123 | 466 | |
daniele | 3:b4047e8a0123 | 467 | if(client->buffer) |
daniele | 3:b4047e8a0123 | 468 | pico_free(client->buffer); |
daniele | 3:b4047e8a0123 | 469 | |
daniele | 3:b4047e8a0123 | 470 | if(client->state != HTTP_CLOSED || !client->sck) |
daniele | 3:b4047e8a0123 | 471 | pico_socket_close(client->sck); |
daniele | 3:b4047e8a0123 | 472 | |
daniele | 3:b4047e8a0123 | 473 | pico_free(client); |
daniele | 3:b4047e8a0123 | 474 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 475 | } |
daniele | 3:b4047e8a0123 | 476 | } |
daniele | 3:b4047e8a0123 | 477 | |
daniele | 3:b4047e8a0123 | 478 | // check the integrity of the request |
daniele | 3:b4047e8a0123 | 479 | int parseRequest(struct httpClient * client) |
daniele | 3:b4047e8a0123 | 480 | { |
daniele | 3:b4047e8a0123 | 481 | char c; |
daniele | 3:b4047e8a0123 | 482 | //read first line |
daniele | 3:b4047e8a0123 | 483 | consumeChar(c); |
daniele | 3:b4047e8a0123 | 484 | if(c == 'G') |
daniele | 3:b4047e8a0123 | 485 | { // possible GET |
daniele | 3:b4047e8a0123 | 486 | |
daniele | 3:b4047e8a0123 | 487 | char line[HTTP_HEADER_MAX_LINE]; |
daniele | 3:b4047e8a0123 | 488 | int index = 0; |
daniele | 3:b4047e8a0123 | 489 | |
daniele | 3:b4047e8a0123 | 490 | line[index] = c; |
daniele | 3:b4047e8a0123 | 491 | |
daniele | 3:b4047e8a0123 | 492 | // consume the full line |
daniele | 3:b4047e8a0123 | 493 | while(consumeChar(c)>0) // read char by char only the first line |
daniele | 3:b4047e8a0123 | 494 | { |
daniele | 3:b4047e8a0123 | 495 | line[++index] = c; |
daniele | 3:b4047e8a0123 | 496 | if(c == '\n') |
daniele | 3:b4047e8a0123 | 497 | break; |
daniele | 3:b4047e8a0123 | 498 | |
daniele | 3:b4047e8a0123 | 499 | if(index >= HTTP_HEADER_MAX_LINE) |
daniele | 3:b4047e8a0123 | 500 | { |
daniele | 3:b4047e8a0123 | 501 | dbg("Size exceeded \n"); |
daniele | 3:b4047e8a0123 | 502 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 503 | } |
daniele | 3:b4047e8a0123 | 504 | } |
daniele | 3:b4047e8a0123 | 505 | |
daniele | 3:b4047e8a0123 | 506 | // extract the function and the resource |
daniele | 3:b4047e8a0123 | 507 | if(memcmp(line,"GET",3u) || line[3u]!=' ' || index < 10u || line[index] !='\n') |
daniele | 3:b4047e8a0123 | 508 | { |
daniele | 3:b4047e8a0123 | 509 | dbg("Wrong command or wrong ending\n"); |
daniele | 3:b4047e8a0123 | 510 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 511 | } |
daniele | 3:b4047e8a0123 | 512 | |
daniele | 3:b4047e8a0123 | 513 | // start reading the resource |
daniele | 3:b4047e8a0123 | 514 | index = 4u; // go after ' ' |
daniele | 3:b4047e8a0123 | 515 | while(line[index]!=' ') |
daniele | 3:b4047e8a0123 | 516 | { |
daniele | 3:b4047e8a0123 | 517 | if(line[index]=='\n') // no terminator ' ' |
daniele | 3:b4047e8a0123 | 518 | { |
daniele | 3:b4047e8a0123 | 519 | dbg("No terminator...\n"); |
daniele | 3:b4047e8a0123 | 520 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 521 | } |
daniele | 3:b4047e8a0123 | 522 | |
daniele | 3:b4047e8a0123 | 523 | index++; |
daniele | 3:b4047e8a0123 | 524 | } |
daniele | 3:b4047e8a0123 | 525 | |
daniele | 3:b4047e8a0123 | 526 | client->resource = pico_zalloc(index - 3u);// allocate without the GET in front + 1 which is \0 |
daniele | 3:b4047e8a0123 | 527 | |
daniele | 3:b4047e8a0123 | 528 | if(!client) |
daniele | 3:b4047e8a0123 | 529 | { |
daniele | 3:b4047e8a0123 | 530 | pico_err = PICO_ERR_ENOMEM; |
daniele | 3:b4047e8a0123 | 531 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 532 | } |
daniele | 3:b4047e8a0123 | 533 | |
daniele | 3:b4047e8a0123 | 534 | // copy the resource |
daniele | 3:b4047e8a0123 | 535 | memcpy(client->resource,line+4u,index-4u);// copy without the \0 which was already set by pico_zalloc |
daniele | 3:b4047e8a0123 | 536 | |
daniele | 3:b4047e8a0123 | 537 | client->state = HTTP_WAIT_EOF_HDR; |
daniele | 3:b4047e8a0123 | 538 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 539 | |
daniele | 3:b4047e8a0123 | 540 | } |
daniele | 3:b4047e8a0123 | 541 | |
daniele | 3:b4047e8a0123 | 542 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 543 | } |
daniele | 3:b4047e8a0123 | 544 | |
daniele | 3:b4047e8a0123 | 545 | |
daniele | 3:b4047e8a0123 | 546 | |
daniele | 3:b4047e8a0123 | 547 | int readRemainingHeader(struct httpClient * client) |
daniele | 3:b4047e8a0123 | 548 | { |
daniele | 3:b4047e8a0123 | 549 | char line[100]; |
daniele | 3:b4047e8a0123 | 550 | int count = 0; |
daniele | 3:b4047e8a0123 | 551 | int len; |
daniele | 3:b4047e8a0123 | 552 | |
daniele | 3:b4047e8a0123 | 553 | while( (len = pico_socket_read(client->sck,line,100u)) > 0) |
daniele | 3:b4047e8a0123 | 554 | { |
daniele | 3:b4047e8a0123 | 555 | char c; |
daniele | 3:b4047e8a0123 | 556 | int index = 0; |
daniele | 3:b4047e8a0123 | 557 | // parse the response |
daniele | 3:b4047e8a0123 | 558 | while(index < len) |
daniele | 3:b4047e8a0123 | 559 | { |
daniele | 3:b4047e8a0123 | 560 | c = line[index++]; |
daniele | 3:b4047e8a0123 | 561 | if(c!='\r' && c!='\n') |
daniele | 3:b4047e8a0123 | 562 | count++; |
daniele | 3:b4047e8a0123 | 563 | if(c=='\n') |
daniele | 3:b4047e8a0123 | 564 | { |
daniele | 3:b4047e8a0123 | 565 | if(!count) |
daniele | 3:b4047e8a0123 | 566 | { |
daniele | 3:b4047e8a0123 | 567 | client->state = HTTP_EOF_HDR; |
daniele | 3:b4047e8a0123 | 568 | dbg("End of header !\n"); |
daniele | 3:b4047e8a0123 | 569 | break; |
daniele | 3:b4047e8a0123 | 570 | } |
daniele | 3:b4047e8a0123 | 571 | count = 0; |
daniele | 3:b4047e8a0123 | 572 | |
daniele | 3:b4047e8a0123 | 573 | } |
daniele | 3:b4047e8a0123 | 574 | } |
daniele | 3:b4047e8a0123 | 575 | } |
daniele | 3:b4047e8a0123 | 576 | |
daniele | 3:b4047e8a0123 | 577 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 578 | } |
daniele | 3:b4047e8a0123 | 579 | |
daniele | 3:b4047e8a0123 | 580 | void sendData(struct httpClient * client) |
daniele | 3:b4047e8a0123 | 581 | { |
daniele | 3:b4047e8a0123 | 582 | int length; |
daniele | 3:b4047e8a0123 | 583 | while( client->bufferSent < client->bufferSize && |
daniele | 3:b4047e8a0123 | 584 | (length = pico_socket_write(client->sck,client->buffer+client->bufferSent,client->bufferSize-client->bufferSent)) > 0 ) |
daniele | 3:b4047e8a0123 | 585 | { |
daniele | 3:b4047e8a0123 | 586 | client->bufferSent += length; |
daniele | 3:b4047e8a0123 | 587 | server.wakeup(EV_HTTP_PROGRESS,client->connectionID); |
daniele | 3:b4047e8a0123 | 588 | } |
daniele | 3:b4047e8a0123 | 589 | |
daniele | 3:b4047e8a0123 | 590 | if(client->bufferSent == client->bufferSize && client->bufferSize) |
daniele | 3:b4047e8a0123 | 591 | { |
daniele | 3:b4047e8a0123 | 592 | //send chunk trail |
daniele | 3:b4047e8a0123 | 593 | if(pico_socket_write(client->sck,"\r\n",2) > 0) |
daniele | 3:b4047e8a0123 | 594 | { |
daniele | 3:b4047e8a0123 | 595 | client->state = HTTP_WAIT_DATA; |
daniele | 3:b4047e8a0123 | 596 | //free the buffer |
daniele | 3:b4047e8a0123 | 597 | pico_free(client->buffer); |
daniele | 3:b4047e8a0123 | 598 | client->buffer = NULL; |
daniele | 3:b4047e8a0123 | 599 | server.wakeup(EV_HTTP_SENT,client->connectionID); |
daniele | 3:b4047e8a0123 | 600 | } |
daniele | 3:b4047e8a0123 | 601 | } |
daniele | 3:b4047e8a0123 | 602 | |
daniele | 3:b4047e8a0123 | 603 | } |
daniele | 3:b4047e8a0123 | 604 | |
daniele | 3:b4047e8a0123 | 605 | int readData(struct httpClient * client) |
daniele | 3:b4047e8a0123 | 606 | { |
daniele | 3:b4047e8a0123 | 607 | if(client->state == HTTP_WAIT_HDR) |
daniele | 3:b4047e8a0123 | 608 | { |
daniele | 3:b4047e8a0123 | 609 | if(parseRequest(client)<0 || readRemainingHeader(client)<0) |
daniele | 3:b4047e8a0123 | 610 | { |
daniele | 3:b4047e8a0123 | 611 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 612 | } |
daniele | 3:b4047e8a0123 | 613 | } // continue with this in case the header comes line by line not a big chunk |
daniele | 3:b4047e8a0123 | 614 | else if(client->state == HTTP_WAIT_EOF_HDR) |
daniele | 3:b4047e8a0123 | 615 | { |
daniele | 3:b4047e8a0123 | 616 | if(readRemainingHeader(client)<0 ) |
daniele | 3:b4047e8a0123 | 617 | return HTTP_RETURN_ERROR; |
daniele | 3:b4047e8a0123 | 618 | } |
daniele | 3:b4047e8a0123 | 619 | |
daniele | 3:b4047e8a0123 | 620 | if(client->state == HTTP_EOF_HDR) |
daniele | 3:b4047e8a0123 | 621 | { |
daniele | 3:b4047e8a0123 | 622 | client->state = HTTP_WAIT_RESPONSE; |
daniele | 3:b4047e8a0123 | 623 | pico_socket_shutdown(client->sck,PICO_SHUT_RD); |
daniele | 3:b4047e8a0123 | 624 | server.wakeup(EV_HTTP_REQ,client->connectionID); |
daniele | 3:b4047e8a0123 | 625 | } |
daniele | 3:b4047e8a0123 | 626 | |
daniele | 3:b4047e8a0123 | 627 | return HTTP_RETURN_OK; |
daniele | 3:b4047e8a0123 | 628 | } |
daniele | 3:b4047e8a0123 | 629 | |
daniele | 3:b4047e8a0123 | 630 | struct httpClient * findClient(uint16_t conn) |
daniele | 3:b4047e8a0123 | 631 | { |
daniele | 3:b4047e8a0123 | 632 | struct httpClient dummy = {.connectionID = conn}; |
daniele | 3:b4047e8a0123 | 633 | |
daniele | 3:b4047e8a0123 | 634 | return pico_tree_findKey(&pico_http_clients,&dummy); |
daniele | 3:b4047e8a0123 | 635 | } |
daniele | 3:b4047e8a0123 | 636 | #endif |