httpsvlt.pp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. {
  2. $Id$
  3. HTTP Servlet Classes
  4. Copyright (c) 2003 by
  5. Areca Systems GmbH / Sebastian Guenther, [email protected]
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. }
  12. unit HTTPSvlt;
  13. interface
  14. uses SysUtils, Classes, fpAsync, fpSock, HTTPBase, Servlets;
  15. resourcestring
  16. SErrUnknownMethod = 'Unknown HTTP method "%s" used';
  17. SErrUnsupportedMethod = 'HTTP method "%s" is not supported for this URL';
  18. type
  19. THttpSession = class
  20. public
  21. property Attributes[const AName: String]: TObject; // !!!: Implement this rw
  22. property CreationTime: TDateTime; // !!!: Implement this
  23. property ID: String; // !!!: Implement this
  24. property LastAccessedTime: TDateTime; // !!!: Implement this
  25. property MaxInactiveInterval: TDateTime; // !!!: Implement this rw
  26. property ServletContext: TServletContext; // !!!: Implement this
  27. property IsNew: Boolean; // !!!: Implement this
  28. // procedure Invalidate; // !!!: Implement this
  29. // procedure RemoveAttribute(const AName: String); // !!!: Implement this
  30. end;
  31. THttpServletRequest = class(TServletRequest)
  32. private
  33. RequestHeader: THTTPRequestHeader;
  34. protected
  35. function GetContentLength: Integer; override;
  36. function GetContentType: String; override;
  37. function GetProtocol: String; override;
  38. function GetMethod: String;
  39. function GetRequestURI: String;
  40. function GetQueryString: String;
  41. public
  42. constructor Create(ARequestHeader: THTTPRequestHeader; AInputStream: TStream;
  43. const AScheme, APathInfo: String);
  44. // GetSession
  45. // function IsRequestedSessionIdFromCookie: Boolean; // !!!: Implement this
  46. // function IsRequestedSessionIdFromURL: Boolean; // !!!: Implement this
  47. // function IsRequestedSessionIdValid: Boolean; // !!!: Implement this
  48. property AuthType: String; // !!!: How to implement?
  49. property ContextPath: String; // !!!: How to implement?
  50. property CookieCount: Integer; // !!!: How to implement?
  51. property Cookies[Index: Integer]: Pointer; // !!!: How to implement?
  52. property DateHeaders[const AName: String]: TDateTime; // !!!: Implement this
  53. property Headers[const AName: String]: String; // !!!: Implement this
  54. property IntHeaders[const AName: String]: Integer; // !!!: Implement this
  55. property Method: String read GetMethod;
  56. property PathInfo: String read FPathInfo;
  57. property PathTranslated: String; // !!!: How to implement?
  58. property QueryString: String read GetQueryString;
  59. property RemoteUser: String; // !!!: How to implement?
  60. property RequestedSessionID: String; // !!!: How to implement?
  61. property RequestURI: String read GetRequestURI;
  62. property RequestURL: String; // !!!: How to implement?
  63. property ServletPath: String; // !!!: How to implement?
  64. end;
  65. THttpServletResponse = class(TServletResponse)
  66. private
  67. ResponseHeader: THTTPResponseHeader;
  68. protected
  69. procedure SetContentType(const Value: String); override;
  70. procedure SetContentLength(Value: Int64); override;
  71. public
  72. constructor Create(AResponseHeader: THTTPResponseHeader;
  73. AOutputStream: TStream);
  74. // procedure AddCookie(Cookie: TCookie); // !!!: Implement this
  75. // procedure AddDateHeader(const AName: String; ADate: TDateTime); // !!!: Implement this
  76. // procedure AddHeader(const AName, AValue: String); // !!!: Implement this
  77. // procedure AddIntHeader(const AName: String; AValue: Int64); // !!!: Implement this
  78. // function ContainsHeader(const AName: String): Boolean; // !!!: Implement this
  79. // function EncodeRedirectURL(const URL: String): String; // !!!: Implement this
  80. // function EncodeURL(const URL: String): String; // !!!: Implement this
  81. // procedure SendError(StatusCode: Integer); // !!!: Implement this
  82. // procedure SendError(StatusCode: Integer; const Msg: String); // !!!: Implement this
  83. // procedure SendRedirect(const Location: String); // !!!: Implement this
  84. // procedure SetDateHeader(const AName: String; ADate: TDateTime); // !!!: Implement this
  85. // procedure SetHeader(const AName, AValue: String); // !!!: Implement this
  86. // procedure SetIntHeader(const AName: String; AValue: Int64); // !!!: Implement this
  87. // procedure SetStatus(StatusCode: Integer); // !!!: Implement this
  88. // procedure SetStatus(StatusCode: Integer; const Msg: String); // !!!: Implement this
  89. end;
  90. THttpServlet = class(TGenericServlet)
  91. protected
  92. // function GetLastModified(Req: THttpServletRequest): TDateTime;
  93. // Handlers for HTTP methods
  94. procedure DoDelete(Req: THttpServletRequest; Resp: THttpServletResponse);
  95. virtual; abstract;
  96. procedure DoGet(Req: THttpServletRequest; Resp: THttpServletResponse);
  97. virtual; abstract;
  98. procedure DoHead(Req: THttpServletRequest; Resp: THttpServletResponse);
  99. virtual; abstract;
  100. procedure DoOptions(Req: THttpServletRequest; Resp: THttpServletResponse);
  101. virtual; abstract;
  102. procedure DoPost(Req: THttpServletRequest; Resp: THttpServletResponse);
  103. virtual; abstract;
  104. procedure DoPut(Req: THttpServletRequest; Resp: THttpServletResponse);
  105. virtual; abstract;
  106. procedure DoTrace(Req: THttpServletRequest; Resp: THttpServletResponse);
  107. virtual; abstract;
  108. procedure Service(Req: THttpServletRequest; Resp: THttpServletResponse); virtual;
  109. end;
  110. // A simple file retreiving servlet
  111. TCustomFileServlet = class(THttpServlet)
  112. private
  113. FPath: String;
  114. protected
  115. procedure DoGet(Req: THttpServletRequest; Resp: THttpServletResponse); override;
  116. property Path: String read FPath write FPath;
  117. end;
  118. TFileServlet = class(TCustomFileServlet)
  119. published
  120. property Path;
  121. end;
  122. // HTTP server (servlet container)
  123. TServletMapping = class(TCollectionItem)
  124. private
  125. FServlet: TGenericServlet;
  126. FURLPattern: String;
  127. published
  128. property Servlet: TGenericServlet read FServlet write FServlet;
  129. property URLPattern: String read FURLPattern write FURLPattern;
  130. end;
  131. TServletMappings = class(TCollection)
  132. private
  133. function GetItem(Index: Integer): TServletMapping;
  134. procedure SetItem(Index: Integer; Value: TServletMapping);
  135. public
  136. property Items[Index: Integer]: TServletMapping read GetItem write SetItem;
  137. default;
  138. end;
  139. THttpServer = class(TCustomTCPServer)
  140. private
  141. Connections: TList; // List of THttpServerConnection objects
  142. FServletMappings: TServletMappings;
  143. protected
  144. procedure DoConnect(AStream: TSocketStream); override;
  145. public
  146. constructor Create(AOwner: TComponent); override;
  147. destructor Destroy; override;
  148. procedure AddServlet(AServlet: THttpServlet; const AURLPattern: String);
  149. // procedure RemoveServlet(const APathName: String);
  150. published
  151. property Active;
  152. property Port;
  153. property OnQueryConnect;
  154. property OnConnect;
  155. property ServletMappings: TServletMappings
  156. read FServletMappings write FServletMappings;
  157. end;
  158. { No, this one really doesn't belong to here - but as soon as we don't have a
  159. nice solution for platform-independent component streaming in the FCL classes
  160. unit, it will be left here. }
  161. function InitInheritedComponent(Instance: TComponent; RootAncestor: TClass): Boolean;
  162. implementation
  163. constructor THttpServletRequest.Create(ARequestHeader: THTTPRequestHeader;
  164. AInputStream: TStream; const AScheme, APathInfo: String);
  165. begin
  166. inherited Create(AInputStream, AScheme, APathInfo);
  167. RequestHeader := ARequestHeader;
  168. end;
  169. function THttpServletRequest.GetContentLength: Integer;
  170. begin
  171. Result := RequestHeader.ContentLength;
  172. end;
  173. function THttpServletRequest.GetContentType: String;
  174. begin
  175. Result := RequestHeader.ContentType;
  176. end;
  177. function THttpServletRequest.GetProtocol: String;
  178. begin
  179. Result := 'HTTP/' + RequestHeader.HttpVersion;
  180. end;
  181. function THttpServletRequest.GetMethod: String;
  182. begin
  183. Result := RequestHeader.Command;
  184. end;
  185. function THttpServletRequest.GetRequestURI: String;
  186. begin
  187. Result := RequestHeader.URI;
  188. end;
  189. function THttpServletRequest.GetQueryString: String;
  190. begin
  191. Result := RequestHeader.QueryString;
  192. end;
  193. constructor THttpServletResponse.Create(AResponseHeader: THTTPResponseHeader;
  194. AOutputStream: TStream);
  195. begin
  196. inherited Create(AOutputStream);
  197. ResponseHeader := AResponseHeader;
  198. end;
  199. procedure THttpServletResponse.SetContentType(const Value: String);
  200. begin
  201. ResponseHeader.ContentType := Value;
  202. end;
  203. procedure THttpServletResponse.SetContentLength(Value: Int64);
  204. begin
  205. ResponseHeader.ContentLength := Value;
  206. end;
  207. procedure THttpServlet.Service(Req: THttpServletRequest; Resp: THttpServletResponse);
  208. var
  209. Method: String;
  210. begin
  211. Method := Req.Method;
  212. try
  213. if Method = 'DELETE' then
  214. DoDelete(Req, Resp)
  215. else if Method = 'GET' then
  216. DoGet(Req, Resp)
  217. else if Method = 'HEAD' then
  218. DoHead(Req, Resp)
  219. else if Method = 'OPTIONS' then
  220. DoOptions(Req, Resp)
  221. else if Method = 'POST' then
  222. DoPost(Req, Resp)
  223. else if Method = 'PUT' then
  224. DoPut(Req, Resp)
  225. else if Method = 'TRACE' then
  226. DoTrace(Req, Resp)
  227. else
  228. raise EServlet.CreateFmt(SErrUnknownMethod, [Method]);
  229. except
  230. on e: EAbstractError do
  231. raise EServlet.CreateFmt(SErrUnsupportedMethod, [Method]);
  232. end;
  233. end;
  234. procedure TCustomFileServlet.DoGet(Req: THttpServletRequest;
  235. Resp: THttpServletResponse);
  236. var
  237. f: TStream;
  238. s: String;
  239. i, LastStart: Integer;
  240. begin
  241. s := Req.PathInfo;
  242. i := 1;
  243. LastStart := 1;
  244. while i <= Length(s) do
  245. begin
  246. if (s[i] = '/') or (s[i] = '\') then
  247. LastStart := i + 1
  248. else if (i = LastStart) and (s[i] = '.') and (i < Length(s)) and
  249. (s[i + 1] = '..') then
  250. exit; // !!!: are ".." allowed in URLs?
  251. Inc(i);
  252. end;
  253. if s = '' then
  254. s := 'index.html';
  255. f := TFileStream.Create(Path + '/' + s, fmOpenRead);
  256. try
  257. Resp.OutputStream.CopyFrom(f, f.Size);
  258. finally
  259. f.Free;
  260. end;
  261. end;
  262. // HTTP Server
  263. function TServletMappings.GetItem(Index: Integer): TServletMapping;
  264. begin
  265. Result := TServletMapping(inherited GetItem(Index));
  266. end;
  267. procedure TServletMappings.SetItem(Index: Integer; Value: TServletMapping);
  268. begin
  269. inherited SetItem(Index, Value);
  270. end;
  271. type
  272. THttpServerConnection = class
  273. private
  274. Server: THttpServer;
  275. Stream: TSocketStream;
  276. RequestHeader: THttpRequestHeader;
  277. RequestStream: TMemoryStream;
  278. ResponseHeader: THttpResponseHeader;
  279. ResponseStream: TMemoryStream;
  280. BytesToRead, BytesToWrite: Integer;
  281. DataAvailableNotifyHandle: Pointer;
  282. CanSendNotifyHandle: Pointer;
  283. SendBuffer: Pointer;
  284. procedure RequestHeaderReceived(Sender: TObject);
  285. procedure DataAvailable(Sender: TObject);
  286. procedure RequestStreamReceived;
  287. procedure ResponseHeaderSent(Sender: TObject);
  288. procedure CanSend(Sender: TObject);
  289. public
  290. constructor Create(AServer: THttpServer; AStream: TSocketStream);
  291. destructor Destroy; override;
  292. end;
  293. constructor THttpServerConnection.Create(AServer: THttpServer;
  294. AStream: TSocketStream);
  295. begin
  296. inherited Create;
  297. Server := AServer;
  298. Stream := AStream;
  299. RequestHeader := THttpRequestHeader.Create;
  300. RequestHeader.OnCompleted := @RequestHeaderReceived;
  301. RequestHeader.AsyncReceive(Server.EventLoop, Stream);
  302. end;
  303. destructor THttpServerConnection.Destroy;
  304. begin
  305. if Assigned(DataAvailableNotifyHandle) then
  306. Server.EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
  307. if Assigned(CanSendNotifyHandle) then
  308. Server.EventLoop.ClearCanWriteNotify(CanSendNotifyHandle);
  309. RequestHeader.Free;
  310. RequestStream.Free;
  311. ResponseHeader.Free;
  312. ResponseStream.Free;
  313. Stream.Free;
  314. Server.Connections.Remove(Self);
  315. inherited Destroy;
  316. end;
  317. procedure THttpServerConnection.RequestHeaderReceived(Sender: TObject);
  318. var
  319. BytesInBuffer: Integer;
  320. NeedMoreData: Boolean;
  321. begin
  322. // WriteLn('HTTP-Header empfangen');
  323. BytesInBuffer:= RequestHeader.Reader.BytesInBuffer;
  324. BytesToRead := RequestHeader.ContentLength;
  325. // WriteLn('Content-Length: ', BytesToRead, ', noch im Puffer: ', BytesInBuffer);
  326. RequestStream := TMemoryStream.Create;
  327. NeedMoreData := RequestHeader.Command = 'POST';
  328. if BytesInBuffer > 0 then
  329. begin
  330. RequestStream.Write(RequestHeader.Reader.Buffer^, BytesInBuffer);
  331. if BytesToRead > 0 then
  332. Dec(BytesToRead, BytesInBuffer);
  333. if BytesInBuffer = RequestHeader.ContentLength then
  334. NeedMoreData := False;
  335. end;
  336. if NeedMoreData then
  337. DataAvailableNotifyHandle := Server.EventLoop.SetDataAvailableNotify(
  338. Stream.Handle, @DataAvailable, nil)
  339. else
  340. RequestStreamReceived;
  341. end;
  342. procedure THttpServerConnection.DataAvailable(Sender: TObject);
  343. var
  344. Buffer: array[0..4095] of Byte;
  345. ReadNow, BytesRead: Integer;
  346. begin
  347. ReadNow := SizeOf(Buffer);
  348. if (BytesToRead > 0) and (ReadNow > BytesToRead) then
  349. ReadNow := BytesToRead;
  350. BytesRead := Stream.Read(Buffer, ReadNow);
  351. // WriteLn('Sollte ', ReadNow, ' Bytes lesen, ', BytesRead, ' wurden gelesen');
  352. RequestStream.Write(Buffer, BytesRead);
  353. if BytesToRead > 0 then
  354. begin
  355. Dec(BytesToRead, BytesRead);
  356. if BytesToRead = 0 then
  357. begin
  358. Server.EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
  359. DataAvailableNotifyHandle := nil;
  360. RequestStreamReceived;
  361. end;
  362. end;
  363. end;
  364. procedure THttpServerConnection.RequestStreamReceived;
  365. var
  366. i: Integer;
  367. s, URI: String;
  368. Servlet: TGenericServlet;
  369. Request: THttpServletRequest;
  370. Response: THttpServletResponse;
  371. begin
  372. // WriteLn('Stream received: ', RequestStream.Size, ' bytes');
  373. URI := UpperCase(RequestHeader.URI);
  374. for i := 0 to Server.ServletMappings.Count - 1 do
  375. begin
  376. s := UpperCase(Server.ServletMappings[i].URLPattern);
  377. if ((s[Length(s)] = '*') and (Copy(s, 1, Length(s) - 1) =
  378. Copy(URI, 1, Length(s) - 1))) or (s = URI) then
  379. break;
  380. end;
  381. if i < Server.ServletMappings.Count then
  382. Servlet := Server.ServletMappings[i].Servlet
  383. else
  384. Servlet := nil;
  385. if RequestHeader.ContentLength = 0 then
  386. RequestHeader.ContentLength := RequestStream.Size;
  387. RequestStream.Position := 0;
  388. if s[Length(s)] = '*' then
  389. s := Copy(s, 1, Length(s) - 1);
  390. Request := THttpServletRequest.Create(RequestHeader, RequestStream, 'http',
  391. Copy(RequestHeader.URI, Length(s) + 1, Length(RequestHeader.URI)));
  392. ResponseHeader := THTTPResponseHeader.Create;
  393. ResponseHeader.Connection := 'Keep-Alive';
  394. ResponseStream := TMemoryStream.Create;
  395. Response := THttpServletResponse.Create(ResponseHeader, ResponseStream);
  396. try
  397. try
  398. if Assigned(Servlet) then
  399. if Servlet.InheritsFrom(THttpServlet) then
  400. THttpServlet(Servlet).Service(Request, Response)
  401. else
  402. Servlet.Service(Request, Response)
  403. else
  404. begin
  405. ResponseHeader.ContentType := 'text/plain';
  406. s := 'Invalid URL';
  407. ResponseStream.Write(s[1], Length(s));
  408. end;
  409. except
  410. on e: Exception do
  411. begin
  412. s := 'An error occured: ' + ' ' + e.Message;
  413. ResponseHeader.ContentType := 'text/plain';
  414. ResponseStream.Write(s[1], Length(s));
  415. end;
  416. end;
  417. BytesToWrite := ResponseStream.Size;
  418. SendBuffer := ResponseStream.Memory;
  419. ResponseStream.Position := 0;
  420. ResponseHeader.ContentLength := BytesToWrite;
  421. ResponseHeader.OnCompleted := @ResponseHeaderSent;
  422. ResponseHeader.AsyncSend(Server.EventLoop, Stream);
  423. finally
  424. Response.Free;
  425. Request.Free;
  426. end;
  427. // WriteLn('Antwort wurde generiert');
  428. end;
  429. procedure THttpServerConnection.ResponseHeaderSent(Sender: TObject);
  430. begin
  431. // WriteLn('Antwortheader geschickt');
  432. if BytesToWrite > 0 then
  433. CanSendNotifyHandle := Server.EventLoop.SetCanWriteNotify(Stream.Handle,
  434. @CanSend, nil);
  435. end;
  436. procedure THttpServerConnection.CanSend(Sender: TObject);
  437. var
  438. BytesWritten: Integer;
  439. begin
  440. BytesWritten := Stream.Write(SendBuffer^, BytesToWrite);
  441. Dec(BytesToWrite, BytesWritten);
  442. Inc(SendBuffer, BytesWritten);
  443. if BytesToWrite = 0 then
  444. begin
  445. // WriteLn('Antwortdaten geschickt');
  446. Free;
  447. end;
  448. end;
  449. constructor THttpServer.Create(AOwner: TComponent);
  450. begin
  451. inherited Create(AOwner);
  452. ServletMappings := TServletMappings.Create(TServletMapping);
  453. end;
  454. destructor THttpServer.Destroy;
  455. var
  456. i: Integer;
  457. begin
  458. ServletMappings.Free;
  459. if Assigned(Connections) then
  460. begin
  461. for i := 0 to Connections.Count - 1 do
  462. THttpServerConnection(Connections[i]).Free;
  463. Connections.Free;
  464. end;
  465. inherited Destroy;
  466. end;
  467. procedure THttpServer.AddServlet(AServlet: THttpServlet;
  468. const AURLPattern: String);
  469. var
  470. Mapping: TServletMapping;
  471. begin
  472. Mapping := TServletMapping(ServletMappings.Add);
  473. Mapping.Servlet := AServlet;
  474. Mapping.URLPattern := AURLPattern;
  475. end;
  476. {procedure THttpServer.RemoveServlet(const APathName: String);
  477. var
  478. i: Integer;
  479. begin
  480. for i := 0 to Servlets.Count - 1 do
  481. if TServletInfo(Servlets[i]).PathName = APathName then
  482. begin
  483. TServletInfo(Servlets[i]).Free;
  484. Servlets.Delete(i);
  485. break;
  486. end;
  487. end;}
  488. procedure THttpServer.DoConnect(AStream: TSocketStream);
  489. begin
  490. // WriteLn('Incoming HTTP connection');
  491. if not Assigned(Connections) then
  492. Connections := TList.Create;
  493. Connections.Add(THttpServerConnection.Create(Self, AStream));
  494. end;
  495. function InitInheritedComponent(Instance: TComponent; RootAncestor: TClass): Boolean;
  496. function DoInitClass(ClassType: TClass): Boolean;
  497. var
  498. Filename: String;
  499. TextStream, BinStream: TStream;
  500. begin
  501. Result := False;
  502. if (ClassType <> TComponent) and (ClassType <> RootAncestor) then
  503. begin
  504. { Init the parent class first }
  505. Result := DoInitClass(ClassType.ClassParent);
  506. Filename := LowerCase(Copy(ClassType.ClassName, 2, 255)) + '.frm';
  507. TextStream := nil;
  508. BinStream := nil;
  509. try
  510. try
  511. TextStream := TFileStream.Create(Filename, fmOpenRead);
  512. except
  513. exit;
  514. end;
  515. BinStream := TMemoryStream.Create;
  516. ObjectTextToBinary(TextStream, BinStream);
  517. BinStream.Position := 0;
  518. BinStream.ReadComponent(Instance);
  519. Result := True;
  520. finally
  521. TextStream.Free;
  522. BinStream.Free;
  523. end;
  524. end;
  525. end;
  526. begin
  527. {!!!: GlobalNameSpace.BeginWrite;
  528. try}
  529. if (Instance.ComponentState * [csLoading, csInline]) = [] then
  530. begin
  531. BeginGlobalLoading;
  532. try
  533. Result := DoInitClass(Instance.ClassType);
  534. NotifyGlobalLoading;
  535. finally
  536. EndGlobalLoading;
  537. end;
  538. end else
  539. Result := DoInitClass(Instance.ClassType);
  540. {finally
  541. GlobalNameSpace.EndWrite;
  542. end;}
  543. end;
  544. end.
  545. {
  546. $Log$
  547. Revision 1.4 2004-01-31 19:13:14 sg
  548. * Splittet old HTTP unit into httpbase and httpclient
  549. * Many improvements in fpSock (e.g. better disconnection detection)
  550. Revision 1.3 2003/11/22 12:01:18 sg
  551. * Adaptions to new version of HTTP unit: All server functionality now is
  552. in this unit, and not http.pp anymore
  553. Revision 1.2 2003/06/25 08:53:51 sg
  554. * Inform the server socket object that it runs non-blocking
  555. Revision 1.1 2002/04/25 19:30:29 sg
  556. * First version (with exception of the HTTP unit: This is an improved version
  557. of the old asyncio HTTP unit, now adapted to fpAsync)
  558. }