source: trunk/Packages/CoolWeb/WebServer/HTTPServer.pas

Last change on this file was 151, checked in by chronos, 9 months ago
File size: 12.8 KB
Line 
1unit HTTPServer;
2
3interface
4
5uses
6 Classes, SysUtils, Common, MemoryStreamEx, MIMEType, Synautil, Syncobjs,
7 Generics.Collections, Generics;
8
9type
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
118procedure HTTPExceptionShow(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
119procedure HTTPExceptionHide(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
120
121resourcestring
122 SEmptyHTTPHandler = 'No handler defined for HTTP server.';
123 SFileNotFound = 'File %s not found.';
124 SPageNotFound = 'Page %s not found.';
125
126const
127 StatusCodeNotFound = '404 Not Found';
128
129
130implementation
131
132{ THTTPServer }
133
134procedure THTTPServer.ServerInfo(HandlerData: THTTPHandlerData);
135var
136 I: Integer;
137 Item: TPair<string, string>;
138begin
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;
187end;
188
189procedure THTTPServer.ErrorResponse(HandlerData: THTTPHandlerData);
190begin
191 with HandlerData, Response.Content do begin
192 Response.StatusCode := StatusCodeNotFound;
193 WriteString('<html><body>' + Format(SPageNotFound, [Implode('/', Request.Path)]) + '</body></html>');
194 end;
195end;
196
197procedure THTTPServer.SetShowExceptions(const AValue: Boolean);
198begin
199 FShowExceptions := AValue;
200 if AValue then ExceptProc := HTTPExceptionShow
201 else ExceptProc := HTTPExceptionHide;
202end;
203
204procedure THTTPServer.Run;
205begin
206end;
207
208procedure THTTPServer.FileResponse(HandlerData: THTTPHandlerData);
209var
210 BinaryFile: TFileStream;
211 FileName: string;
212begin
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;
229end;
230
231constructor THTTPServer.Create(AOwner: TComponent);
232begin
233 inherited;
234 Lock := TCriticalSection.Create;
235 ShowExceptions := True;
236 DocumentRoot := './';
237end;
238
239destructor THTTPServer.Destroy;
240begin
241 FreeAndNil(Lock);
242 inherited;
243end;
244
245{ THTTPResponse }
246
247procedure THTTPResponse.Assign(Source: THTTPResponse);
248begin
249 StatusCode := Source.StatusCode;
250 Content.Assign(Source.Content);
251 ContentType := Source.ContentType;
252 Cookies.Assign(Source.Cookies);
253 Headers.Assign(Source.Headers);
254end;
255
256procedure THTTPResponse.Clear;
257begin
258 Content.Clear;
259 Cookies.Clear;
260 Headers.Clear;
261end;
262
263constructor THTTPResponse.Create;
264begin
265 StatusCode := '200 OK';
266 Content := TMemoryStreamEx.Create;
267 Cookies := TCookieList.Create;
268 Headers := TDictionaryStringString.Create;
269end;
270
271destructor THTTPResponse.Destroy;
272begin
273 FreeAndNil(Content);
274 FreeAndNil(Headers);
275 FreeAndNil(Cookies);
276 inherited;
277end;
278
279{ TRequestHandlerList }
280
281function TRequestHandlerList.AddNew(AName: string; AHandler: TRequestEvent): TRequestHandler;
282begin
283 Result := TRequestHandler.Create;
284 Result.Name := AName;
285 Result.Handler := AHandler;
286 Add(Result);
287end;
288
289function TRequestHandlerList.IndexOfName(AName: string): TRequestHandler;
290var
291 I: Integer;
292begin
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;
297end;
298
299{ THTTPRequest }
300
301procedure THTTPRequest.Assign(Source: THTTPRequest);
302begin
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);
311end;
312
313procedure THTTPRequest.Clear;
314begin
315 Post.Clear;
316 Content.Clear;
317 Path.Clear;
318 Cookies.Clear;
319 Headers.Clear;
320 Query.Clear;
321end;
322
323constructor THTTPRequest.Create;
324begin
325 Post := TQueryParameterList.Create;
326 Query := TQueryParameterList.Create;
327 Path := TListString.Create;
328 Headers := TDictionaryStringString.Create;
329 Cookies := TCookieList.Create;
330 Content := TMemoryStreamEx.Create;
331end;
332
333destructor THTTPRequest.Destroy;
334begin
335 FreeAndNil(Content);
336 FreeAndNil(Post);
337 FreeAndNil(Query);
338 FreeAndNil(Path);
339 FreeAndNil(Headers);
340 FreeAndNil(Cookies);
341 inherited;
342end;
343
344{ TQueryParameterList }
345
346procedure TQueryParameterList.Parse(Text: string);
347var
348 I: Integer;
349 Parts: TListString;
350 Pair: TListString;
351begin
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;
366end;
367
368function TQueryParameterList.Syntetize: string;
369var
370 Item: TPair<string, string>;
371begin
372 Result := '';
373 for Item in Self do
374 Result := Result + '&amp;' + Item.Key + '=' + Item.Value;
375 Result := Copy(Result, 6, Length(Result));
376end;
377
378{ TCookieList }
379
380procedure TCookieList.Parse(Text: string);
381var
382 I: Integer;
383 Parts: TListString;
384 Pair: TListString;
385begin
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;
400end;
401
402function TCookieList.Syntetize: string;
403var
404 Item: TPair<string, string>;
405begin
406 Result := '';
407 for Item in Self do
408 Result := Result + '; ' + Item.Key + '=' + Item.Value;
409 Result := Copy(Result, 2, Length(Result));
410end;
411
412{ THTTPSessionStorage }
413
414procedure THTTPSessionStorage.Load(HandlerData: THTTPHandlerData);
415begin
416end;
417
418procedure THTTPSessionStorage.Save(HandlerData: THTTPHandlerData);
419begin
420end;
421
422constructor THTTPSessionStorage.Create;
423begin
424 inherited;
425end;
426
427destructor THTTPSessionStorage.Destroy;
428begin
429 inherited;
430end;
431
432{ THTTPHandlerData }
433
434procedure THTTPHandlerData.Assign(Source: THTTPHandlerData);
435begin
436 Request.Assign(Source.Request);
437 Response.Assign(Source.Response);
438 Session.Assign(Source.Session);
439 Server := Source.Server;
440 SessionId := Source.SessionId;
441end;
442
443constructor THTTPHandlerData.Create;
444begin
445 Request := THTTPRequest.Create;
446 Response := THTTPResponse.Create;
447 Session := TStringList.Create;
448end;
449
450destructor THTTPHandlerData.Destroy;
451begin
452 FreeAndNil(Request);
453 FreeAndNil(Response);
454 FreeAndNil(Session);
455 inherited;
456end;
457
458procedure HTTPExceptionShow(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
459type
460 TArrayOfPointer = array of Pointer;
461var
462 Message: string;
463 I: LongInt;
464 hstdout: ^Text;
465begin
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^, '');
485end;
486
487procedure HTTPExceptionHide(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
488type
489 TArrayOfPointer = array of Pointer;
490var
491 Message: string;
492 I: LongInt;
493 hstdout: ^Text;
494 hstderr: ^Text;
495begin
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^, '');
517end;
518
519end.
520
Note: See TracBrowser for help on using the repository browser.