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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
/****************************************************************************************************
RepRapFirmware - Webserver
This class serves a single-page web applications to the attached network. This page forms the user's
interface with the RepRap machine. This software interprests returned values from the page and uses it
to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like
temperature and uses those to construct the web page.
The page itself - reprap.htm - uses Knockout.js and Jquery.js. See:
http://knockoutjs.com/
http://jquery.com/
-----------------------------------------------------------------------------------------------------
Version 0.2
10 May 2013
Adrian Bowyer
RepRap Professional Ltd
http://reprappro.com
Licence: GPL
****************************************************************************************************/
#ifndef WEBSERVER_H
#define WEBSERVER_H
/* Generic values */
const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2
const unsigned int TCP_MSS = 1460;
class ConnectionState;
class NetworkTransaction;
/* HTTP */
#define KO_START "rr_"
#define KO_FIRST 3
const uint16_t webMessageLength = TCP_MSS; // maximum length of the web message we accept after decoding
const size_t minHttpResponseSize = 768; // minimum number of bytes required for an HTTP response
const size_t maxCommandWords = 4; // max number of space-separated words in the command
const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier
const size_t maxHeaders = 16; // max number of key/value pairs in the headers
const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions
const uint32_t httpSessionTimeout = 8000; // HTTP session timeout in milliseconds
/* FTP */
const uint16_t ftpMessageLength = 128; // maximum line length for incoming FTP commands
const uint32_t ftpPasvPortTimeout = 10000; // maximum time to wait for an FTP data connection in milliseconds
/* Telnet */
const uint32_t telnetSetupDuration = 4000; // ignore the first Telnet request within this duration (in ms)
class Webserver;
// List of protocols that can execute G-Codes
enum class WebSource
{
HTTP,
Telnet
};
// This is the abstract class for all supported protocols
// Any inherited class should implement a state machine to increase performance and reduce memory usage.
class ProtocolInterpreter
{
public:
ProtocolInterpreter(Platform *p, Webserver *ws, Network *n);
virtual ~ProtocolInterpreter() { } // to keep Eclipse happy
virtual void Diagnostics(MessageType mtype) = 0;
virtual void Spin();
virtual void ConnectionEstablished();
virtual void ConnectionLost(const ConnectionState *cs) { }
virtual bool CanParseData();
virtual bool CharFromClient(const char c) = 0;
virtual void NoMoreDataAvailable();
virtual bool DoingFastUpload() const;
virtual void DoFastUpload();
void CancelUpload(); // may be called from ISR!
protected:
Platform *platform;
Webserver *webserver;
Network *network;
// Information for file uploading
enum UploadState
{
notUploading, // no upload in progress
uploadOK, // upload in progress, no error so far
uploadError // upload in progress but had error
};
UploadState uploadState;
FileData fileBeingUploaded;
char filenameBeingUploaded[FILENAME_LENGTH];
bool StartUpload(FileStore *file, const char *fileName);
bool IsUploading() const;
bool FinishUpload(uint32_t fileLength);
};
class Webserver
{
public:
friend class Platform;
friend class ProtocolInterpreter;
Webserver(Platform* p, Network *n);
void Init();
void Spin();
void Exit();
void Diagnostics(MessageType mtype);
bool GCodeAvailable(const WebSource source) const;
char ReadGCode(const WebSource source);
void HandleGCodeReply(const WebSource source, OutputBuffer *reply);
void HandleGCodeReply(const WebSource source, const char *reply);
uint32_t GetReplySeq() const;
// Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version)
uint16_t GetGCodeBufferSpace(const WebSource source) const;
void ConnectionLost(const ConnectionState *cs);
void ConnectionError();
protected:
class HttpInterpreter : public ProtocolInterpreter
{
public:
HttpInterpreter(Platform *p, Webserver *ws, Network *n);
void Spin();
void Diagnostics(MessageType mtype) override;
void ConnectionLost(const ConnectionState *cs);
bool CanParseData() override;
bool CharFromClient(const char c) override;
void NoMoreDataAvailable() override;
void ResetState();
void ResetSessions();
bool DoingFastUpload() const override;
void DoFastUpload();
bool GCodeAvailable() const;
char ReadGCode();
void HandleGCodeReply(OutputBuffer *reply);
void HandleGCodeReply(const char *reply);
uint16_t GetGCodeBufferSpace() const;
uint32_t GetReplySeq() const;
private:
// HTTP server state enumeration. The order is important, in particular xxxEsc1 must follow xxx, and xxxEsc2 must follow xxxEsc1.
// We assume that qualifier keys do not contain escapes, because none of ours needs to be encoded. If we are sent escapes in the key,
// it won't do any harm, but the key won't be recognised even if it would be valid were it decoded.
enum HttpState
{
doingCommandWord, // receiving a word in the first line of the HTTP request
doingFilename, // receiving the filename (second word in the command line)
doingFilenameEsc1, // received '%' in the filename (e.g. we are being asked for a filename with spaces in it)
doingFilenameEsc2, // received '%' and one hex digit in the filename
doingQualifierKey, // receiving a key name in the HTTP request
doingQualifierValue, // receiving a key value in the HTTP request
doingQualifierValueEsc1, // received '%' in the qualifier
doingQualifierValueEsc2, // received '%' and one hex digit in the qualifier
doingHeaderKey, // receiving a header key
expectingHeaderValue, // expecting a header value
doingHeaderValue, // receiving a header value
doingHeaderContinuation // received a newline after a header value
};
HttpState state;
struct KeyValueIndices
{
const char* key;
const char* value;
};
void SendFile(const char* nameOfFileToSend, bool isWebFile);
void SendGCodeReply();
void SendJsonResponse(const char* command);
void GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen);
bool ProcessMessage();
bool RejectMessage(const char* s, unsigned int code = 500);
// Buffers for processing HTTP input
char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers
size_t clientPointer; // current index into clientMessage
char decodeChar;
const char* commandWords[maxCommandWords];
KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls
KeyValueIndices headers[maxHeaders]; // offsets into clientHeader of the key/value pairs
size_t numCommandWords;
size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys
size_t numHeaderKeys; // number of keys we have found, <= maxHeaders
// HTTP sessions
struct HttpSession
{
uint32_t ip;
uint32_t lastQueryTime;
bool isPostUploading;
uint16_t postPort;
};
HttpSession sessions[maxHttpSessions];
uint8_t numSessions;
uint8_t clientsServed;
bool Authenticate();
bool IsAuthenticated() const;
void UpdateAuthentication();
bool RemoveAuthentication();
// Deal with incoming G-Codes
char gcodeBuffer[gcodeBufferLength];
uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
void LoadGcodeBuffer(const char* gc);
void ProcessGcode(const char* gc);
void StoreGcodeData(const char* data, uint16_t len);
// Responses from GCodes class
uint32_t seq; // Sequence number for G-Code replies
OutputStack *gcodeReply;
// File uploads
uint32_t postFileLength, uploadedBytes; // How many POST bytes do we expect and how many have already been written?
time_t fileLastModified;
// Deferred requests (rr_fileinfo)
ConnectionState * volatile deferredRequestConnection; // Which connection expects a response for a deferred request?
char filenameBeingProcessed[FILENAME_LENGTH]; // The filename being processed (for rr_fileinfo)
void ProcessDeferredRequest();
};
HttpInterpreter *httpInterpreter;
class FtpInterpreter : public ProtocolInterpreter
{
public:
FtpInterpreter(Platform *p, Webserver *ws, Network *n);
void Diagnostics(MessageType mtype) override;
void ConnectionEstablished() override;
void ConnectionLost(const ConnectionState *cs) override;
bool CharFromClient(const char c) override;
void ResetState();
bool DoingFastUpload() const override;
private:
enum FtpState
{
idle, // no client connected
authenticating, // not logged in
authenticated, // logged in
waitingForPasvPort, // waiting for connection to be established on PASV port
pasvPortConnected, // client connected to PASV port, ready to send data
doingPasvIO // client is connected and data is being transferred
};
FtpState state;
uint8_t connectedClients;
char clientMessage[ftpMessageLength];
size_t clientPointer;
char filename[FILENAME_LENGTH];
char currentDir[FILENAME_LENGTH];
uint32_t portOpenTime;
void ProcessLine();
void SendReply(int code, const char *message, bool keepConnection = true);
void SendFeatures();
void ReadFilename(uint16_t start);
void ChangeDirectory(const char *newDirectory);
};
FtpInterpreter *ftpInterpreter;
class TelnetInterpreter : public ProtocolInterpreter
{
public:
TelnetInterpreter(Platform *p, Webserver *ws, Network *n);
void Diagnostics(MessageType mtype) override;
void ConnectionEstablished() override;
void ConnectionLost(const ConnectionState *cs) override;
bool CanParseData() override;
bool CharFromClient(const char c) override;
void ResetState();
bool GCodeAvailable() const;
char ReadGCode();
void HandleGCodeReply(OutputBuffer *reply);
void HandleGCodeReply(const char *reply);
uint16_t GetGCodeBufferSpace() const;
void SendGCodeReply();
private:
enum TelnetState
{
idle, // not connected
justConnected, // not logged in, but the client has just connected
authenticating, // not logged in
authenticated // logged in
};
TelnetState state;
uint8_t connectedClients;
uint32_t connectTime;
bool processNextLine;
char clientMessage[GCODE_LENGTH];
size_t clientPointer;
bool ProcessLine();
// Deal with incoming G-Codes
char gcodeBuffer[gcodeBufferLength];
uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
void ProcessGcode(const char* gc);
void StoreGcodeData(const char* data, uint16_t len);
// Converted response from GCodes class (NL -> CRNL)
OutputBuffer * volatile gcodeReply;
};
TelnetInterpreter *telnetInterpreter;
private:
Platform* platform;
Network* network;
bool webserverActive;
NetworkTransaction *currentTransaction;
ConnectionState * volatile readingConnection;
float longWait;
};
inline bool ProtocolInterpreter::CanParseData() { return true; }
inline bool ProtocolInterpreter::DoingFastUpload() const { return false; }
inline bool ProtocolInterpreter::IsUploading() const { return uploadState != notUploading; }
inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); }
inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; }
inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
#endif
|