123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 |
- {
- HTTP Servlet Classes
- Copyright (c) 2003 by
- Areca Systems GmbH / Sebastian Guenther, [email protected]
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- }
- unit HTTPSvlt;
- interface
- uses SysUtils, Classes, fpAsync, fpSock, HTTPBase, Servlets;
- resourcestring
- SErrUnknownMethod = 'Unknown HTTP method "%s" used';
- SErrUnsupportedMethod = 'HTTP method "%s" is not supported for this URL';
- type
- THttpSession = class
- public
- property Attributes[const AName: String]: TObject; // !!!: Implement this rw
- property CreationTime: TDateTime; // !!!: Implement this
- property ID: String; // !!!: Implement this
- property LastAccessedTime: TDateTime; // !!!: Implement this
- property MaxInactiveInterval: TDateTime; // !!!: Implement this rw
- property ServletContext: TServletContext; // !!!: Implement this
- property IsNew: Boolean; // !!!: Implement this
- // procedure Invalidate; // !!!: Implement this
- // procedure RemoveAttribute(const AName: String); // !!!: Implement this
- end;
- THttpServletRequest = class(TServletRequest)
- private
- RequestHeader: THTTPRequestHeader;
- protected
- function GetContentLength: Integer; override;
- function GetContentType: String; override;
- function GetProtocol: String; override;
- function GetMethod: String;
- function GetRequestURI: String;
- function GetQueryString: String;
- public
- constructor Create(ARequestHeader: THTTPRequestHeader; AInputStream: TStream;
- const AScheme, APathInfo: String);
- // GetSession
- // function IsRequestedSessionIdFromCookie: Boolean; // !!!: Implement this
- // function IsRequestedSessionIdFromURL: Boolean; // !!!: Implement this
- // function IsRequestedSessionIdValid: Boolean; // !!!: Implement this
- property AuthType: String; // !!!: How to implement?
- property ContextPath: String; // !!!: How to implement?
- property CookieCount: Integer; // !!!: How to implement?
- property Cookies[Index: Integer]: Pointer; // !!!: How to implement?
- property DateHeaders[const AName: String]: TDateTime; // !!!: Implement this
- property Headers[const AName: String]: String; // !!!: Implement this
- property IntHeaders[const AName: String]: Integer; // !!!: Implement this
- property Method: String read GetMethod;
- property PathInfo: String read FPathInfo;
- property PathTranslated: String; // !!!: How to implement?
- property QueryString: String read GetQueryString;
- property RemoteUser: String; // !!!: How to implement?
- property RequestedSessionID: String; // !!!: How to implement?
- property RequestURI: String read GetRequestURI;
- property RequestURL: String; // !!!: How to implement?
- property ServletPath: String; // !!!: How to implement?
- end;
- THttpServletResponse = class(TServletResponse)
- private
- ResponseHeader: THTTPResponseHeader;
- protected
- procedure SetContentType(const Value: String); override;
- procedure SetContentLength(Value: Int64); override;
- public
- constructor Create(AResponseHeader: THTTPResponseHeader;
- AOutputStream: TStream);
- // procedure AddCookie(Cookie: TCookie); // !!!: Implement this
- // procedure AddDateHeader(const AName: String; ADate: TDateTime); // !!!: Implement this
- // procedure AddHeader(const AName, AValue: String); // !!!: Implement this
- // procedure AddIntHeader(const AName: String; AValue: Int64); // !!!: Implement this
- // function ContainsHeader(const AName: String): Boolean; // !!!: Implement this
- // function EncodeRedirectURL(const URL: String): String; // !!!: Implement this
- // function EncodeURL(const URL: String): String; // !!!: Implement this
- // procedure SendError(StatusCode: Integer); // !!!: Implement this
- // procedure SendError(StatusCode: Integer; const Msg: String); // !!!: Implement this
- // procedure SendRedirect(const Location: String); // !!!: Implement this
- // procedure SetDateHeader(const AName: String; ADate: TDateTime); // !!!: Implement this
- // procedure SetHeader(const AName, AValue: String); // !!!: Implement this
- // procedure SetIntHeader(const AName: String; AValue: Int64); // !!!: Implement this
- // procedure SetStatus(StatusCode: Integer); // !!!: Implement this
- // procedure SetStatus(StatusCode: Integer; const Msg: String); // !!!: Implement this
- end;
- THttpServlet = class(TGenericServlet)
- protected
- // function GetLastModified(Req: THttpServletRequest): TDateTime;
- // Handlers for HTTP methods
- procedure DoDelete(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure DoGet(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure DoHead(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure DoOptions(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure DoPost(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure DoPut(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure DoTrace(Req: THttpServletRequest; Resp: THttpServletResponse);
- virtual; abstract;
- procedure Service(Req: THttpServletRequest; Resp: THttpServletResponse); virtual;
- end;
- // A simple file retreiving servlet
- TCustomFileServlet = class(THttpServlet)
- private
- FPath: String;
- protected
- procedure DoGet(Req: THttpServletRequest; Resp: THttpServletResponse); override;
- property Path: String read FPath write FPath;
- end;
- TFileServlet = class(TCustomFileServlet)
- published
- property Path;
- end;
- // HTTP server (servlet container)
- TServletMapping = class(TCollectionItem)
- private
- FServlet: TGenericServlet;
- FURLPattern: String;
- published
- property Servlet: TGenericServlet read FServlet write FServlet;
- property URLPattern: String read FURLPattern write FURLPattern;
- end;
- TServletMappings = class(TCollection)
- private
- function GetItem(Index: Integer): TServletMapping;
- procedure SetItem(Index: Integer; Value: TServletMapping);
- public
- property Items[Index: Integer]: TServletMapping read GetItem write SetItem;
- default;
- end;
- THttpServer = class(TCustomTCPServer)
- private
- Connections: TList; // List of THttpServerConnection objects
- FServletMappings: TServletMappings;
- protected
- procedure DoConnect(AStream: TSocketStream); override;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure AddServlet(AServlet: THttpServlet; const AURLPattern: String);
- // procedure RemoveServlet(const APathName: String);
- published
- property Active;
- property Port;
- property OnQueryConnect;
- property OnConnect;
- property ServletMappings: TServletMappings
- read FServletMappings write FServletMappings;
- end;
- { No, this one really doesn't belong to here - but as soon as we don't have a
- nice solution for platform-independent component streaming in the FCL classes
- unit, it will be left here. }
- function InitInheritedComponent(Instance: TComponent; RootAncestor: TClass): Boolean;
- implementation
- constructor THttpServletRequest.Create(ARequestHeader: THTTPRequestHeader;
- AInputStream: TStream; const AScheme, APathInfo: String);
- begin
- inherited Create(AInputStream, AScheme, APathInfo);
- RequestHeader := ARequestHeader;
- end;
- function THttpServletRequest.GetContentLength: Integer;
- begin
- Result := RequestHeader.ContentLength;
- end;
- function THttpServletRequest.GetContentType: String;
- begin
- Result := RequestHeader.ContentType;
- end;
- function THttpServletRequest.GetProtocol: String;
- begin
- Result := 'HTTP/' + RequestHeader.HttpVersion;
- end;
- function THttpServletRequest.GetMethod: String;
- begin
- Result := RequestHeader.Command;
- end;
- function THttpServletRequest.GetRequestURI: String;
- begin
- Result := RequestHeader.URI;
- end;
- function THttpServletRequest.GetQueryString: String;
- begin
- Result := RequestHeader.QueryString;
- end;
- constructor THttpServletResponse.Create(AResponseHeader: THTTPResponseHeader;
- AOutputStream: TStream);
- begin
- inherited Create(AOutputStream);
- ResponseHeader := AResponseHeader;
- end;
- procedure THttpServletResponse.SetContentType(const Value: String);
- begin
- ResponseHeader.ContentType := Value;
- end;
- procedure THttpServletResponse.SetContentLength(Value: Int64);
- begin
- ResponseHeader.ContentLength := Value;
- end;
- procedure THttpServlet.Service(Req: THttpServletRequest; Resp: THttpServletResponse);
- var
- Method: String;
- begin
- Method := Req.Method;
- try
- if Method = 'DELETE' then
- DoDelete(Req, Resp)
- else if Method = 'GET' then
- DoGet(Req, Resp)
- else if Method = 'HEAD' then
- DoHead(Req, Resp)
- else if Method = 'OPTIONS' then
- DoOptions(Req, Resp)
- else if Method = 'POST' then
- DoPost(Req, Resp)
- else if Method = 'PUT' then
- DoPut(Req, Resp)
- else if Method = 'TRACE' then
- DoTrace(Req, Resp)
- else
- raise EServlet.CreateFmt(SErrUnknownMethod, [Method]);
- except
- on e: EAbstractError do
- raise EServlet.CreateFmt(SErrUnsupportedMethod, [Method]);
- end;
- end;
- procedure TCustomFileServlet.DoGet(Req: THttpServletRequest;
- Resp: THttpServletResponse);
- var
- f: TStream;
- s: String;
- i, LastStart: Integer;
- begin
- s := Req.PathInfo;
- i := 1;
- LastStart := 1;
- while i <= Length(s) do
- begin
- if (s[i] = '/') or (s[i] = '\') then
- LastStart := i + 1
- else if (i = LastStart) and (s[i] = '.') and (i < Length(s)) and
- (s[i + 1] = '..') then
- exit; // !!!: are ".." allowed in URLs?
- Inc(i);
- end;
- if s = '' then
- s := 'index.html';
- f := TFileStream.Create(Path + '/' + s, fmOpenRead);
- try
- Resp.OutputStream.CopyFrom(f, f.Size);
- finally
- f.Free;
- end;
- end;
- // HTTP Server
- function TServletMappings.GetItem(Index: Integer): TServletMapping;
- begin
- Result := TServletMapping(inherited GetItem(Index));
- end;
- procedure TServletMappings.SetItem(Index: Integer; Value: TServletMapping);
- begin
- inherited SetItem(Index, Value);
- end;
- type
- THttpServerConnection = class
- private
- Server: THttpServer;
- Stream: TSocketStream;
- RequestHeader: THttpRequestHeader;
- RequestStream: TMemoryStream;
- ResponseHeader: THttpResponseHeader;
- ResponseStream: TMemoryStream;
- BytesToRead, BytesToWrite: Integer;
- DataAvailableNotifyHandle: Pointer;
- CanSendNotifyHandle: Pointer;
- SendBuffer: Pointer;
- procedure RequestHeaderReceived(Sender: TObject);
- procedure DataAvailable(Sender: TObject);
- procedure RequestStreamReceived;
- procedure ResponseHeaderSent(Sender: TObject);
- procedure CanSend(Sender: TObject);
- public
- constructor Create(AServer: THttpServer; AStream: TSocketStream);
- destructor Destroy; override;
- end;
- constructor THttpServerConnection.Create(AServer: THttpServer;
- AStream: TSocketStream);
- begin
- inherited Create;
- Server := AServer;
- Stream := AStream;
- RequestHeader := THttpRequestHeader.Create;
- RequestHeader.OnCompleted := @RequestHeaderReceived;
- RequestHeader.AsyncReceive(Server.EventLoop, Stream);
- end;
- destructor THttpServerConnection.Destroy;
- begin
- if Assigned(DataAvailableNotifyHandle) then
- Server.EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
- if Assigned(CanSendNotifyHandle) then
- Server.EventLoop.ClearCanWriteNotify(CanSendNotifyHandle);
- RequestHeader.Free;
- RequestStream.Free;
- ResponseHeader.Free;
- ResponseStream.Free;
- Stream.Free;
- Server.Connections.Remove(Self);
- inherited Destroy;
- end;
- procedure THttpServerConnection.RequestHeaderReceived(Sender: TObject);
- var
- BytesInBuffer: Integer;
- NeedMoreData: Boolean;
- begin
- // WriteLn('HTTP-Header empfangen');
- BytesInBuffer:= RequestHeader.Reader.BytesInBuffer;
- BytesToRead := RequestHeader.ContentLength;
- // WriteLn('Content-Length: ', BytesToRead, ', noch im Puffer: ', BytesInBuffer);
- RequestStream := TMemoryStream.Create;
- NeedMoreData := RequestHeader.Command = 'POST';
- if BytesInBuffer > 0 then
- begin
- RequestStream.Write(RequestHeader.Reader.Buffer^, BytesInBuffer);
- if BytesToRead > 0 then
- Dec(BytesToRead, BytesInBuffer);
- if BytesInBuffer = RequestHeader.ContentLength then
- NeedMoreData := False;
- end;
- if NeedMoreData then
- DataAvailableNotifyHandle := Server.EventLoop.SetDataAvailableNotify(
- Stream.Handle, @DataAvailable, nil)
- else
- RequestStreamReceived;
- end;
- procedure THttpServerConnection.DataAvailable(Sender: TObject);
- var
- Buffer: array[0..4095] of Byte;
- ReadNow, BytesRead: Integer;
- begin
- ReadNow := SizeOf(Buffer);
- if (BytesToRead > 0) and (ReadNow > BytesToRead) then
- ReadNow := BytesToRead;
- BytesRead := Stream.Read(Buffer, ReadNow);
- // WriteLn('Sollte ', ReadNow, ' Bytes lesen, ', BytesRead, ' wurden gelesen');
- RequestStream.Write(Buffer, BytesRead);
- if BytesToRead > 0 then
- begin
- Dec(BytesToRead, BytesRead);
- if BytesToRead = 0 then
- begin
- Server.EventLoop.ClearDataAvailableNotify(DataAvailableNotifyHandle);
- DataAvailableNotifyHandle := nil;
- RequestStreamReceived;
- end;
- end;
- end;
- procedure THttpServerConnection.RequestStreamReceived;
- var
- i: Integer;
- s, URI: String;
- Servlet: TGenericServlet;
- Request: THttpServletRequest;
- Response: THttpServletResponse;
- begin
- // WriteLn('Stream received: ', RequestStream.Size, ' bytes');
- URI := UpperCase(RequestHeader.URI);
- for i := 0 to Server.ServletMappings.Count - 1 do
- begin
- s := UpperCase(Server.ServletMappings[i].URLPattern);
- if ((s[Length(s)] = '*') and (Copy(s, 1, Length(s) - 1) =
- Copy(URI, 1, Length(s) - 1))) or (s = URI) then
- break;
- end;
- if i < Server.ServletMappings.Count then
- Servlet := Server.ServletMappings[i].Servlet
- else
- Servlet := nil;
- if RequestHeader.ContentLength = 0 then
- RequestHeader.ContentLength := RequestStream.Size;
- RequestStream.Position := 0;
- if s[Length(s)] = '*' then
- s := Copy(s, 1, Length(s) - 1);
- Request := THttpServletRequest.Create(RequestHeader, RequestStream, 'http',
- Copy(RequestHeader.URI, Length(s) + 1, Length(RequestHeader.URI)));
- ResponseHeader := THTTPResponseHeader.Create;
- ResponseHeader.Connection := 'Keep-Alive';
- ResponseStream := TMemoryStream.Create;
- Response := THttpServletResponse.Create(ResponseHeader, ResponseStream);
- try
- try
- if Assigned(Servlet) then
- if Servlet.InheritsFrom(THttpServlet) then
- THttpServlet(Servlet).Service(Request, Response)
- else
- Servlet.Service(Request, Response)
- else
- begin
- ResponseHeader.ContentType := 'text/plain';
- s := 'Invalid URL';
- ResponseStream.Write(s[1], Length(s));
- end;
- except
- on e: Exception do
- begin
- s := 'An error occured: ' + ' ' + e.Message;
- ResponseHeader.ContentType := 'text/plain';
- ResponseStream.Write(s[1], Length(s));
- end;
- end;
- BytesToWrite := ResponseStream.Size;
- SendBuffer := ResponseStream.Memory;
- ResponseStream.Position := 0;
- ResponseHeader.ContentLength := BytesToWrite;
- ResponseHeader.OnCompleted := @ResponseHeaderSent;
- ResponseHeader.AsyncSend(Server.EventLoop, Stream);
- finally
- Response.Free;
- Request.Free;
- end;
- // WriteLn('Antwort wurde generiert');
- end;
- procedure THttpServerConnection.ResponseHeaderSent(Sender: TObject);
- begin
- // WriteLn('Antwortheader geschickt');
- if BytesToWrite > 0 then
- CanSendNotifyHandle := Server.EventLoop.SetCanWriteNotify(Stream.Handle,
- @CanSend, nil);
- end;
- procedure THttpServerConnection.CanSend(Sender: TObject);
- var
- BytesWritten: Integer;
- begin
- BytesWritten := Stream.Write(SendBuffer^, BytesToWrite);
- Dec(BytesToWrite, BytesWritten);
- Inc(SendBuffer, BytesWritten);
- if BytesToWrite = 0 then
- begin
- // WriteLn('Antwortdaten geschickt');
- Free;
- end;
- end;
- constructor THttpServer.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- ServletMappings := TServletMappings.Create(TServletMapping);
- end;
- destructor THttpServer.Destroy;
- var
- i: Integer;
- begin
- ServletMappings.Free;
- if Assigned(Connections) then
- begin
- for i := 0 to Connections.Count - 1 do
- THttpServerConnection(Connections[i]).Free;
- Connections.Free;
- end;
- inherited Destroy;
- end;
- procedure THttpServer.AddServlet(AServlet: THttpServlet;
- const AURLPattern: String);
- var
- Mapping: TServletMapping;
- begin
- Mapping := TServletMapping(ServletMappings.Add);
- Mapping.Servlet := AServlet;
- Mapping.URLPattern := AURLPattern;
- end;
- {procedure THttpServer.RemoveServlet(const APathName: String);
- var
- i: Integer;
- begin
- for i := 0 to Servlets.Count - 1 do
- if TServletInfo(Servlets[i]).PathName = APathName then
- begin
- TServletInfo(Servlets[i]).Free;
- Servlets.Delete(i);
- break;
- end;
- end;}
- procedure THttpServer.DoConnect(AStream: TSocketStream);
- begin
- // WriteLn('Incoming HTTP connection');
- if not Assigned(Connections) then
- Connections := TList.Create;
- Connections.Add(THttpServerConnection.Create(Self, AStream));
- end;
- function InitInheritedComponent(Instance: TComponent; RootAncestor: TClass): Boolean;
- function DoInitClass(ClassType: TClass): Boolean;
- var
- Filename: String;
- TextStream, BinStream: TStream;
- begin
- Result := False;
- if (ClassType <> TComponent) and (ClassType <> RootAncestor) then
- begin
- { Init the parent class first }
- Result := DoInitClass(ClassType.ClassParent);
- Filename := LowerCase(Copy(ClassType.ClassName, 2, 255)) + '.frm';
- TextStream := nil;
- BinStream := nil;
- try
- try
- TextStream := TFileStream.Create(Filename, fmOpenRead);
- except
- exit;
- end;
- BinStream := TMemoryStream.Create;
- ObjectTextToBinary(TextStream, BinStream);
- BinStream.Position := 0;
- BinStream.ReadComponent(Instance);
- Result := True;
- finally
- TextStream.Free;
- BinStream.Free;
- end;
- end;
- end;
- begin
- {!!!: GlobalNameSpace.BeginWrite;
- try}
- if (Instance.ComponentState * [csLoading, csInline]) = [] then
- begin
- BeginGlobalLoading;
- try
- Result := DoInitClass(Instance.ClassType);
- NotifyGlobalLoading;
- finally
- EndGlobalLoading;
- end;
- end else
- Result := DoInitClass(Instance.ClassType);
- {finally
- GlobalNameSpace.EndWrite;
- end;}
- end;
- end.
|