1 | unit HTTPServer;
|
---|
2 |
|
---|
3 | interface
|
---|
4 |
|
---|
5 | uses
|
---|
6 | Classes, SysUtils, Common, MemoryStreamEx, MIMEType, Synautil, Syncobjs,
|
---|
7 | Generics.Collections, Generics;
|
---|
8 |
|
---|
9 | type
|
---|
10 | THTTPServer = class;
|
---|
11 | EEmptyHTTPHandler = class(Exception);
|
---|
12 |
|
---|
13 | TQueryParameterList = class(TDictionaryStringString)
|
---|
14 | procedure Parse(Text: string);
|
---|
15 | function Syntetize: string;
|
---|
16 | end;
|
---|
17 |
|
---|
18 | { TCookieList }
|
---|
19 |
|
---|
20 | TCookieList = class(TDictionaryStringString)
|
---|
21 | procedure Parse(Text: string);
|
---|
22 | function Syntetize: string;
|
---|
23 | end;
|
---|
24 |
|
---|
25 | { THTTPRequest }
|
---|
26 |
|
---|
27 | THTTPRequest = class
|
---|
28 | ContentType: string;
|
---|
29 | Content: TMemoryStreamEx;
|
---|
30 | Query: TQueryParameterList;
|
---|
31 | Path: TListString;
|
---|
32 | Method: string;
|
---|
33 | Headers: TDictionaryStringString;
|
---|
34 | Cookies: TCookieList;
|
---|
35 | Post: TQueryParameterList;
|
---|
36 | procedure Assign(Source: THTTPRequest);
|
---|
37 | procedure Clear;
|
---|
38 | constructor Create;
|
---|
39 | destructor Destroy; override;
|
---|
40 | end;
|
---|
41 |
|
---|
42 | { THTTPResponse }
|
---|
43 |
|
---|
44 | THTTPResponse = class
|
---|
45 | StatusCode: string;
|
---|
46 | ContentType: string;
|
---|
47 | Content: TMemoryStreamEx;
|
---|
48 | Headers: TDictionaryStringString;
|
---|
49 | Cookies: TCookieList;
|
---|
50 | procedure Assign(Source: THTTPResponse);
|
---|
51 | procedure Clear;
|
---|
52 | constructor Create;
|
---|
53 | destructor Destroy; override;
|
---|
54 | end;
|
---|
55 |
|
---|
56 | { THTTPHandlerData }
|
---|
57 |
|
---|
58 | THTTPHandlerData = class
|
---|
59 | Server: THTTPServer;
|
---|
60 | Request: THTTPRequest;
|
---|
61 | Response: THTTPResponse;
|
---|
62 | SessionId: string;
|
---|
63 | Session: TStringList;
|
---|
64 | procedure Assign(Source: THTTPHandlerData);
|
---|
65 | constructor Create; virtual;
|
---|
66 | destructor Destroy; override;
|
---|
67 | end;
|
---|
68 |
|
---|
69 | TRequestEvent = procedure(HandlerData: THTTPHandlerData) of object;
|
---|
70 |
|
---|
71 | TRequestHandler = class
|
---|
72 | Name: string;
|
---|
73 | Handler: TRequestEvent;
|
---|
74 | end;
|
---|
75 |
|
---|
76 | { TRequestHandlerList }
|
---|
77 |
|
---|
78 | TRequestHandlerList = class(TObjectList<TRequestHandler>)
|
---|
79 | function AddNew(AName: string; AHandler: TRequestEvent): TRequestHandler;
|
---|
80 | function IndexOfName(AName: string): TRequestHandler;
|
---|
81 | end;
|
---|
82 |
|
---|
83 | { THTTPSessionStorage }
|
---|
84 |
|
---|
85 | THTTPSessionStorage = class(TComponent)
|
---|
86 | public
|
---|
87 | procedure Load(HandlerData: THTTPHandlerData); virtual;
|
---|
88 | procedure Save(HandlerData: THTTPHandlerData); virtual;
|
---|
89 | constructor Create(AOwner: TComponent); override;
|
---|
90 | destructor Destroy; override;
|
---|
91 | end;
|
---|
92 |
|
---|
93 | { THTTPServer }
|
---|
94 |
|
---|
95 | THTTPServer = class(TComponent)
|
---|
96 | private
|
---|
97 | FDocumentRoot: string;
|
---|
98 | FOnRequest: TRequestEvent;
|
---|
99 | FSessionStorage: THTTPSessionStorage;
|
---|
100 | FShowExceptions: Boolean;
|
---|
101 | procedure SetShowExceptions(const AValue: Boolean);
|
---|
102 | public
|
---|
103 | Lock: TCriticalSection;
|
---|
104 | procedure Run; virtual;
|
---|
105 | procedure ErrorResponse(HandlerData: THTTPHandlerData);
|
---|
106 | procedure FileResponse(HandlerData: THTTPHandlerData);
|
---|
107 | procedure ServerInfo(HandlerData: THTTPHandlerData); virtual;
|
---|
108 | constructor Create(AOwner: TComponent); override;
|
---|
109 | destructor Destroy; override;
|
---|
110 | published
|
---|
111 | property ShowExceptions: Boolean read FShowExceptions write SetShowExceptions;
|
---|
112 | property DocumentRoot: string read FDocumentRoot write FDocumentRoot;
|
---|
113 | property SessionStorage: THTTPSessionStorage read FSessionStorage
|
---|
114 | write FSessionStorage;
|
---|
115 | property OnRequest: TRequestEvent read FOnRequest write FOnRequest;
|
---|
116 | end;
|
---|
117 |
|
---|
118 | procedure HTTPExceptionShow(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
|
---|
119 | procedure HTTPExceptionHide(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
|
---|
120 |
|
---|
121 | resourcestring
|
---|
122 | SEmptyHTTPHandler = 'No handler defined for HTTP server.';
|
---|
123 | SFileNotFound = 'File %s not found.';
|
---|
124 | SPageNotFound = 'Page %s not found.';
|
---|
125 |
|
---|
126 | const
|
---|
127 | StatusCodeNotFound = '404 Not Found';
|
---|
128 |
|
---|
129 |
|
---|
130 | implementation
|
---|
131 |
|
---|
132 | { THTTPServer }
|
---|
133 |
|
---|
134 | procedure THTTPServer.ServerInfo(HandlerData: THTTPHandlerData);
|
---|
135 | var
|
---|
136 | I: Integer;
|
---|
137 | Item: TPair<string, string>;
|
---|
138 | begin
|
---|
139 | with HandlerData, Response.Content do begin
|
---|
140 | WriteString('<a href="?ServerInfo">Refresh</a>');
|
---|
141 |
|
---|
142 | WriteString('<h5>Request HTTP content:</h5>');
|
---|
143 | WriteStream(Request.Content, Request.Content.Size);
|
---|
144 |
|
---|
145 | WriteString('<h5>Request HTTP headers</h5>');
|
---|
146 | for Item in Request.Headers do begin;
|
---|
147 | WriteString(Item.Key + ': ' + Item.Value + '<br/>');
|
---|
148 | end;
|
---|
149 |
|
---|
150 | WriteString('<h5>Request HTTP GET</h5>');
|
---|
151 | for Item in Request.Query do begin
|
---|
152 | WriteString(Item.Key + ': ' + Item.Value + '<br/>');
|
---|
153 | end;
|
---|
154 |
|
---|
155 | WriteString('<h5>Request HTTP cookies</h5>');
|
---|
156 | for I := 0 to Request.Cookies.Count - 1 do begin
|
---|
157 | WriteString(Item.Key + ': ' + Item.Value + '<br/>');
|
---|
158 | end;
|
---|
159 |
|
---|
160 | //WriteString('Session id: ' + SessionId);
|
---|
161 | WriteString('<h5>Session variables</h5>');
|
---|
162 | for I := 0 to Session.Count - 1 do begin
|
---|
163 | with Session do
|
---|
164 | WriteString(Names[I] + ': ' + ValueFromIndex[I] + '<br/>');
|
---|
165 | end;
|
---|
166 |
|
---|
167 | WriteString('<h5>Request HTTP POST</h5>');
|
---|
168 | for Item in Request.Post do begin
|
---|
169 | WriteString(Item.Key + ': ' + Item.Value + '<br/>');
|
---|
170 | end;
|
---|
171 |
|
---|
172 |
|
---|
173 | WriteString('<h5>Response HTTP content:</h5>');
|
---|
174 | WriteStream(Response.Content, Response.Content.Size);
|
---|
175 |
|
---|
176 | WriteString('<h5>Response HTTP headers</h5>');
|
---|
177 | with Response.Content do
|
---|
178 | for Item in Response.Headers do begin
|
---|
179 | WriteString(Item.Key + ': ' + Item.Value + '<br/>');
|
---|
180 | end;
|
---|
181 |
|
---|
182 | WriteString('<h5>Response HTTP cookies</h5>');
|
---|
183 | for Item in Response.Cookies do begin;
|
---|
184 | WriteString(Item.Key + ': ' + Item.Value + '<br/>');
|
---|
185 | end;
|
---|
186 | end;
|
---|
187 | end;
|
---|
188 |
|
---|
189 | procedure THTTPServer.ErrorResponse(HandlerData: THTTPHandlerData);
|
---|
190 | begin
|
---|
191 | with HandlerData, Response.Content do begin
|
---|
192 | Response.StatusCode := StatusCodeNotFound;
|
---|
193 | WriteString('<html><body>' + Format(SPageNotFound, [Implode('/', Request.Path)]) + '</body></html>');
|
---|
194 | end;
|
---|
195 | end;
|
---|
196 |
|
---|
197 | procedure THTTPServer.SetShowExceptions(const AValue: Boolean);
|
---|
198 | begin
|
---|
199 | FShowExceptions := AValue;
|
---|
200 | if AValue then ExceptProc := HTTPExceptionShow
|
---|
201 | else ExceptProc := HTTPExceptionHide;
|
---|
202 | end;
|
---|
203 |
|
---|
204 | procedure THTTPServer.Run;
|
---|
205 | begin
|
---|
206 | end;
|
---|
207 |
|
---|
208 | procedure THTTPServer.FileResponse(HandlerData: THTTPHandlerData);
|
---|
209 | var
|
---|
210 | BinaryFile: TFileStream;
|
---|
211 | FileName: string;
|
---|
212 | begin
|
---|
213 | with HandlerData do begin
|
---|
214 | FileName := DocumentRoot + DirectorySeparator + Implode('/', Request.Path);
|
---|
215 | if FileExists(FileName) then begin
|
---|
216 | Response.Headers.Items['Content-Type'] := GetMIMEType(Copy(ExtractFileExt(FileName), 2, 255));
|
---|
217 | try
|
---|
218 | BinaryFile := TFileStream.Create(FileName, fmOpenRead);
|
---|
219 | Response.Content.WriteStream(BinaryFile, BinaryFile.Size);
|
---|
220 | finally
|
---|
221 | BinaryFile.Free;
|
---|
222 | end;
|
---|
223 | end else
|
---|
224 | with Response.Content do begin
|
---|
225 | Response.StatusCode := StatusCodeNotFound;
|
---|
226 | WriteString('<html><body>' + Format(SFileNotFound, [Implode('/', Request.Path)]) + '</body></html>');
|
---|
227 | end;
|
---|
228 | end;
|
---|
229 | end;
|
---|
230 |
|
---|
231 | constructor THTTPServer.Create(AOwner: TComponent);
|
---|
232 | begin
|
---|
233 | inherited;
|
---|
234 | Lock := TCriticalSection.Create;
|
---|
235 | ShowExceptions := True;
|
---|
236 | DocumentRoot := './';
|
---|
237 | end;
|
---|
238 |
|
---|
239 | destructor THTTPServer.Destroy;
|
---|
240 | begin
|
---|
241 | FreeAndNil(Lock);
|
---|
242 | inherited;
|
---|
243 | end;
|
---|
244 |
|
---|
245 | { THTTPResponse }
|
---|
246 |
|
---|
247 | procedure THTTPResponse.Assign(Source: THTTPResponse);
|
---|
248 | begin
|
---|
249 | StatusCode := Source.StatusCode;
|
---|
250 | Content.Assign(Source.Content);
|
---|
251 | ContentType := Source.ContentType;
|
---|
252 | Cookies.Assign(Source.Cookies);
|
---|
253 | Headers.Assign(Source.Headers);
|
---|
254 | end;
|
---|
255 |
|
---|
256 | procedure THTTPResponse.Clear;
|
---|
257 | begin
|
---|
258 | Content.Clear;
|
---|
259 | Cookies.Clear;
|
---|
260 | Headers.Clear;
|
---|
261 | end;
|
---|
262 |
|
---|
263 | constructor THTTPResponse.Create;
|
---|
264 | begin
|
---|
265 | StatusCode := '200 OK';
|
---|
266 | Content := TMemoryStreamEx.Create;
|
---|
267 | Cookies := TCookieList.Create;
|
---|
268 | Headers := TDictionaryStringString.Create;
|
---|
269 | end;
|
---|
270 |
|
---|
271 | destructor THTTPResponse.Destroy;
|
---|
272 | begin
|
---|
273 | FreeAndNil(Content);
|
---|
274 | FreeAndNil(Headers);
|
---|
275 | FreeAndNil(Cookies);
|
---|
276 | inherited;
|
---|
277 | end;
|
---|
278 |
|
---|
279 | { TRequestHandlerList }
|
---|
280 |
|
---|
281 | function TRequestHandlerList.AddNew(AName: string; AHandler: TRequestEvent): TRequestHandler;
|
---|
282 | begin
|
---|
283 | Result := TRequestHandler.Create;
|
---|
284 | Result.Name := AName;
|
---|
285 | Result.Handler := AHandler;
|
---|
286 | Add(Result);
|
---|
287 | end;
|
---|
288 |
|
---|
289 | function TRequestHandlerList.IndexOfName(AName: string): TRequestHandler;
|
---|
290 | var
|
---|
291 | I: Integer;
|
---|
292 | begin
|
---|
293 | I := 0;
|
---|
294 | while (I < Count) and (Items[I].Name <> AName) do Inc(I);
|
---|
295 | if I < Count then Result := Items[I]
|
---|
296 | else Result := nil;
|
---|
297 | end;
|
---|
298 |
|
---|
299 | { THTTPRequest }
|
---|
300 |
|
---|
301 | procedure THTTPRequest.Assign(Source: THTTPRequest);
|
---|
302 | begin
|
---|
303 | Content.Assign(Source.Content);
|
---|
304 | ContentType := Source.ContentType;
|
---|
305 | Cookies.Assign(Source.Cookies);
|
---|
306 | Headers.Assign(Source.Headers);
|
---|
307 | Post.Assign(Source.Post);
|
---|
308 | Method := Source.Method;
|
---|
309 | Query.Assign(Source.Query);
|
---|
310 | Path.Assign(Source.Path);
|
---|
311 | end;
|
---|
312 |
|
---|
313 | procedure THTTPRequest.Clear;
|
---|
314 | begin
|
---|
315 | Post.Clear;
|
---|
316 | Content.Clear;
|
---|
317 | Path.Clear;
|
---|
318 | Cookies.Clear;
|
---|
319 | Headers.Clear;
|
---|
320 | Query.Clear;
|
---|
321 | end;
|
---|
322 |
|
---|
323 | constructor THTTPRequest.Create;
|
---|
324 | begin
|
---|
325 | Post := TQueryParameterList.Create;
|
---|
326 | Query := TQueryParameterList.Create;
|
---|
327 | Path := TListString.Create;
|
---|
328 | Headers := TDictionaryStringString.Create;
|
---|
329 | Cookies := TCookieList.Create;
|
---|
330 | Content := TMemoryStreamEx.Create;
|
---|
331 | end;
|
---|
332 |
|
---|
333 | destructor THTTPRequest.Destroy;
|
---|
334 | begin
|
---|
335 | FreeAndNil(Content);
|
---|
336 | FreeAndNil(Post);
|
---|
337 | FreeAndNil(Query);
|
---|
338 | FreeAndNil(Path);
|
---|
339 | FreeAndNil(Headers);
|
---|
340 | FreeAndNil(Cookies);
|
---|
341 | inherited;
|
---|
342 | end;
|
---|
343 |
|
---|
344 | { TQueryParameterList }
|
---|
345 |
|
---|
346 | procedure TQueryParameterList.Parse(Text: string);
|
---|
347 | var
|
---|
348 | I: Integer;
|
---|
349 | Parts: TListString;
|
---|
350 | Pair: TListString;
|
---|
351 | begin
|
---|
352 | try
|
---|
353 | Parts := TListString.Create;
|
---|
354 | Pair := TListString.Create;
|
---|
355 | Clear;
|
---|
356 | Parts.Explode('&', Text);
|
---|
357 | for I := 0 to Parts.Count - 1 do begin
|
---|
358 | Pair.Explode('=', Parts[I]);
|
---|
359 | if Pair.Count >= 2 then
|
---|
360 | Add(Pair[0], Pair[1]);
|
---|
361 | end;
|
---|
362 | finally
|
---|
363 | Parts.Free;
|
---|
364 | Pair.Free;
|
---|
365 | end;
|
---|
366 | end;
|
---|
367 |
|
---|
368 | function TQueryParameterList.Syntetize: string;
|
---|
369 | var
|
---|
370 | Item: TPair<string, string>;
|
---|
371 | begin
|
---|
372 | Result := '';
|
---|
373 | for Item in Self do
|
---|
374 | Result := Result + '&' + Item.Key + '=' + Item.Value;
|
---|
375 | Result := Copy(Result, 6, Length(Result));
|
---|
376 | end;
|
---|
377 |
|
---|
378 | { TCookieList }
|
---|
379 |
|
---|
380 | procedure TCookieList.Parse(Text: string);
|
---|
381 | var
|
---|
382 | I: Integer;
|
---|
383 | Parts: TListString;
|
---|
384 | Pair: TListString;
|
---|
385 | begin
|
---|
386 | try
|
---|
387 | Parts := TListString.Create;
|
---|
388 | Pair := TListString.Create;
|
---|
389 | Clear;
|
---|
390 | Parts.Explode(';', Text);
|
---|
391 | for I := 0 to Parts.Count - 1 do begin
|
---|
392 | Pair.Explode('=', Parts[I]);
|
---|
393 | if Pair.Count >= 2 then
|
---|
394 | Add(Trim(Pair[0]), Trim(Pair[1]));
|
---|
395 | end;
|
---|
396 | finally
|
---|
397 | Pair.Free;
|
---|
398 | Parts.Free;
|
---|
399 | end;
|
---|
400 | end;
|
---|
401 |
|
---|
402 | function TCookieList.Syntetize: string;
|
---|
403 | var
|
---|
404 | Item: TPair<string, string>;
|
---|
405 | begin
|
---|
406 | Result := '';
|
---|
407 | for Item in Self do
|
---|
408 | Result := Result + '; ' + Item.Key + '=' + Item.Value;
|
---|
409 | Result := Copy(Result, 2, Length(Result));
|
---|
410 | end;
|
---|
411 |
|
---|
412 | { THTTPSessionStorage }
|
---|
413 |
|
---|
414 | procedure THTTPSessionStorage.Load(HandlerData: THTTPHandlerData);
|
---|
415 | begin
|
---|
416 | end;
|
---|
417 |
|
---|
418 | procedure THTTPSessionStorage.Save(HandlerData: THTTPHandlerData);
|
---|
419 | begin
|
---|
420 | end;
|
---|
421 |
|
---|
422 | constructor THTTPSessionStorage.Create;
|
---|
423 | begin
|
---|
424 | inherited;
|
---|
425 | end;
|
---|
426 |
|
---|
427 | destructor THTTPSessionStorage.Destroy;
|
---|
428 | begin
|
---|
429 | inherited;
|
---|
430 | end;
|
---|
431 |
|
---|
432 | { THTTPHandlerData }
|
---|
433 |
|
---|
434 | procedure THTTPHandlerData.Assign(Source: THTTPHandlerData);
|
---|
435 | begin
|
---|
436 | Request.Assign(Source.Request);
|
---|
437 | Response.Assign(Source.Response);
|
---|
438 | Session.Assign(Source.Session);
|
---|
439 | Server := Source.Server;
|
---|
440 | SessionId := Source.SessionId;
|
---|
441 | end;
|
---|
442 |
|
---|
443 | constructor THTTPHandlerData.Create;
|
---|
444 | begin
|
---|
445 | Request := THTTPRequest.Create;
|
---|
446 | Response := THTTPResponse.Create;
|
---|
447 | Session := TStringList.Create;
|
---|
448 | end;
|
---|
449 |
|
---|
450 | destructor THTTPHandlerData.Destroy;
|
---|
451 | begin
|
---|
452 | FreeAndNil(Request);
|
---|
453 | FreeAndNil(Response);
|
---|
454 | FreeAndNil(Session);
|
---|
455 | inherited;
|
---|
456 | end;
|
---|
457 |
|
---|
458 | procedure HTTPExceptionShow(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
|
---|
459 | type
|
---|
460 | TArrayOfPointer = array of Pointer;
|
---|
461 | var
|
---|
462 | Message: string;
|
---|
463 | I: LongInt;
|
---|
464 | hstdout: ^Text;
|
---|
465 | begin
|
---|
466 | hstdout := @stdout;
|
---|
467 | WriteLn(hstdout^, 'Content-type: text/html');
|
---|
468 | WriteLn(hstdout^);
|
---|
469 | Writeln(hstdout^, 'An unhandled exception occurred at $', HexStr(PtrUInt(Addr), SizeOf(PtrUInt) * 2), ' :<br/>');
|
---|
470 | if Obj is exception then
|
---|
471 | begin
|
---|
472 | Message := Exception(Obj).ClassName + ' : ' + Exception(Obj).Message;
|
---|
473 | Writeln(hstdout^, Message + '<br/>');
|
---|
474 | end
|
---|
475 | else
|
---|
476 | WriteLn(hstdout^, 'Exception object ', Obj.ClassName, ' is not of class Exception.<br/>');
|
---|
477 | WriteLn(hstdout^, BackTraceStrFunc(Addr) + '<br/>');
|
---|
478 | if (FrameCount > 0) then
|
---|
479 | begin
|
---|
480 | for I := 0 to FrameCount - 1 do
|
---|
481 | if I < Length(TArrayOfPointer(Frames)) then
|
---|
482 | WriteLn(hstdout^, BackTraceStrFunc(TArrayOfPointer(Frames)[I]) + '<br/>');
|
---|
483 | end;
|
---|
484 | WriteLn(hstdout^, '');
|
---|
485 | end;
|
---|
486 |
|
---|
487 | procedure HTTPExceptionHide(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
|
---|
488 | type
|
---|
489 | TArrayOfPointer = array of Pointer;
|
---|
490 | var
|
---|
491 | Message: string;
|
---|
492 | I: LongInt;
|
---|
493 | hstdout: ^Text;
|
---|
494 | hstderr: ^Text;
|
---|
495 | begin
|
---|
496 | hstdout := @stdout;
|
---|
497 | hstderr := @stderr;
|
---|
498 | WriteLn(hstdout^, 'Content-type: text/html');
|
---|
499 | WriteLn(hstdout^);
|
---|
500 | WriteLn(hstdout^, 'Error occured during page generation.');
|
---|
501 | WriteLn(hstderr^, 'An unhandled exception occurred at $', HexStr(PtrUInt(Addr), SizeOf(PtrUInt) * 2), ' :');
|
---|
502 | if Obj is exception then
|
---|
503 | begin
|
---|
504 | Message := Exception(Obj).ClassName + ' : ' + Exception(Obj).Message;
|
---|
505 | WriteLn(hstderr^, Message);
|
---|
506 | end
|
---|
507 | else
|
---|
508 | WriteLn(hstderr^, 'Exception object ', Obj.ClassName, ' is not of class Exception.');
|
---|
509 | WriteLn(hstderr^, BackTraceStrFunc(Addr));
|
---|
510 | if (FrameCount > 0) then
|
---|
511 | begin
|
---|
512 | for I := 0 to FrameCount - 1 do
|
---|
513 | if I < Length(TArrayOfPointer(Frames)) then
|
---|
514 | WriteLn(hstderr^, BackTraceStrFunc(TArrayOfPointer(Frames)[I]));
|
---|
515 | end;
|
---|
516 | WriteLn(hstderr^, '');
|
---|
517 | end;
|
---|
518 |
|
---|
519 | end.
|
---|
520 |
|
---|