-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhttpServer.hpp
More file actions
257 lines (206 loc) · 7.62 KB
/
Copy pathhttpServer.hpp
File metadata and controls
257 lines (206 loc) · 7.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/**
* @file
* @brief Header file containing the HTTP Server class and the HTTP parser
*/
#pragma once
#include "httpRemoteClient.hpp"
#include "httpRequestHandler.hpp"
#include "httpLog.hpp"
namespace http
{
class Server ;
template < class Client_t >
RequestMessage parseRawMessageFrom( const http::RemoteClient < Client_t > & ) ;
}
/// The HTTP Server Class, managing the requests from remote clients
class http::Server
{
http::Log logBuffer ;
public:
Server() ;
template < class Client_t >
void replyTo( const RemoteClient < Client_t > & ) ;
ResponseHeader defaultResponseHeader ;
// Callback functions for HTTP requests
#define DECLARE_REQUEST_HANDLER_PTR(name) \
IMPLEMENT_HTTP_REQUEST_HANDLER( ( * name ) ) = nullptr
DECLARE_REQUEST_HANDLER_PTR( OPTIONS_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( GET_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( HEAD_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( POST_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( PUT_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( DELETE_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( TRACE_requestHandler ) ;
DECLARE_REQUEST_HANDLER_PTR( CONNECT_requestHandler ) ;
#undef DECLARE_REQUEST_HANDLER_PTR
http::LogView log = http::LogView( logBuffer ) ;
} ;
// ******************
// * IMPLEMENTATION *
// ******************
http::Server::Server()
{
// Set default header
defaultResponseHeader.version = "1.1" ;
defaultResponseHeader.connection = "close" ;
defaultResponseHeader.server = "Arduino_HTTP/1.0.0 Arduino" ;
// Set default request handlers
GET_requestHandler = requestHandler::returnTestPage ;
HEAD_requestHandler = requestHandler::returnDefaultHeader ;
}
/**
* Method used to reply to remote client requests
* The request type and the appropriate handler are automatically deduced from
* the request header
*
* @param Client_t The class representing the socket of the transport layer
* @param client A RemoteClient object representing the remote client
*/
template < class Client_t >
void http::Server::replyTo( const RemoteClient < Client_t > & client )
{
if( ! client.available() ) return ;
logBuffer << "[INFO] New client connected" ;
RequestMessage inboundMessage = parseRawMessageFrom( client ) ;
logBuffer << "---------------------" ;
logBuffer << inboundMessage ;
logBuffer << "---------------------" ;
ResponseMessage responseMessage( defaultResponseHeader ) ;
const Response_t & responseCode = responseMessage.header.responseCode ;
if( inboundMessage.parsingFailed )
{
logBuffer << "[ERROR] inboundMessage parsing failed" ;
responseCode = Response::BAD_REQUEST ;
}
else
{
const Request & requestMethod = inboundMessage.header.requestMethod ;
bool requestMethodIsInvalid = false ;
// Select the appropriate function to handle the request
auto requestHandler =
requestMethod == Request::OPTIONS ? OPTIONS_requestHandler :
requestMethod == Request::GET ? GET_requestHandler :
requestMethod == Request::HEAD ? HEAD_requestHandler :
requestMethod == Request::POST ? POST_requestHandler :
requestMethod == Request::PUT ? PUT_requestHandler :
requestMethod == Request::DELETE ? DELETE_requestHandler :
requestMethod == Request::TRACE ? TRACE_requestHandler :
requestMethod == Request::CONNECT ? CONNECT_requestHandler :
( requestMethodIsInvalid = true , nullptr ) ;
if( requestMethodIsInvalid )
{
logBuffer << "[ERROR] Invalid request method" ;
responseCode = Response::BAD_REQUEST ;
}
else if( requestHandler == nullptr )
{
logBuffer << "[FAIL] Request method not allowed by this server" ;
responseCode = Response::NOT_IMPLEMENTED ;
}
else requestHandler( inboundMessage, responseMessage ) ;
}
logBuffer << "[INFO] Sending a " + ( String ) responseCode + " response" ;
client.write( responseMessage ) ;
client.close() ;
}
/**
* The parsing function used to deserialize incoming HTTP messages
*
* @param Client_t The class representing the socket of the transport layer
* @param client A RemoteClient object representing the remote client
*/
template < class Client_t >
http::RequestMessage http::parseRawMessageFrom( const http::RemoteClient < Client_t > & client )
{
RequestMessage requestMessage ;
// Manually read the first byte to let getNextWord and accessNextField
// work as expected
char charBuffer = client.read() ;
auto getNextWord = [ & ]() -> String
{
String word ;
// Read from the buffer until a word-terminating char or field end is encoutered
while( charBuffer != ' ' && charBuffer != '\n' )
{
word += charBuffer ;
charBuffer = client.read() ;
}
// This is necessary to not get stuck on next call
if( charBuffer == ' ' )
charBuffer = client.read() ;
return word ;
} ;
auto accessNextField = [ & ]() -> void
{
// Discard all chars until the next field
while( charBuffer != '\n' )
charBuffer = client.read() ;
// Begin reading the new field, so getNextWord doesn't get stuck
charBuffer = client.read() ;
} ;
// Parse first line
// Parse request method
{
String requestMethod = getNextWord() ;
#define ifMatchesThenAssign(x) requestMethod == ( Request_t ) x ? x
requestMessage.header.requestMethod =
ifMatchesThenAssign( Request::CONNECT ) :
ifMatchesThenAssign( Request::DELETE ) :
ifMatchesThenAssign( Request::GET ) :
ifMatchesThenAssign( Request::HEAD ) :
ifMatchesThenAssign( Request::OPTIONS ) :
ifMatchesThenAssign( Request::POST ) :
ifMatchesThenAssign( Request::PUT ) :
ifMatchesThenAssign( Request::TRACE ) : Request::INVALID ;
#undef ifMatchesThenAssign
}
requestMessage.header.requestTarget = getNextWord() ;
requestMessage.header.version = getNextWord() ;
if(
requestMessage.header.requestMethod == Request::INVALID ||
requestMessage.header.requestTarget == ""
)
{
requestMessage.parsingFailed = true ;
return requestMessage ;
}
// Parse header fields until the header-payload separator or end-of-message is encoutered
while( charBuffer != '\r' && client.available() )
{
accessNextField() ;
// Get the field name
const String parsedTag = getNextWord() ;
/* As HTTP/1.1 specification states, if a field starts with a space ignore it
*
* IMPLEMENTATION NOTE:
* Since getNextWord() stops extracting the word on every whitespace
* if the field begins with a whitespace the first word extracted
* for that field will be empty
*/
if( parsedTag == "" ) continue ;
// Go through all the header fields to find one with a matching name
// if no one is found the current field is ignored
for( uint8_t n = 0 ; n < requestMessage.header.fieldN ; ++ n )
{
Field & field = * requestMessage.header.fieldArray[ n ] ;
if( field.tag != parsedTag ) continue ;
// Read the field value, appending eventual spaces discarded by getNextWord()
for( String parsedValue = getNextWord() ; parsedValue != "" ; parsedValue = getNextWord() )
field.value += parsedValue + ( charBuffer == ' ' ? " " : "" ) ;
break ;
}
}
// Read the payload
accessNextField() ;
/* IMPLEMENTATION NOTE :
* This method allows to correctly read the payload even when using
* keep-alive connections
*/
while( charBuffer != '\r' && client.available() )
{
const String nextWord = getNextWord() ;
if( nextWord == "" ) accessNextField() ;
else requestMessage.payload += nextWord + ( charBuffer == ' ' ? " " : "" ) ;
}
return requestMessage ;
}