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