{ $Project$ $Workfile$ $Revision$ $DateUTC$ $Id$ This file is part of the Indy (Internet Direct) project, and is offered under the dual-licensing agreement described on the Indy website. (http://www.indyproject.org/) Copyright: (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. } { $Log$ } { Rev 1.7 6/26/2004 12:11:16 AM BGooijen updates for D8 Rev 1.6 4/8/2004 4:00:40 PM BGooijen Fix for D8 Rev 1.5 07/04/2004 20:44:06 HHariri Updates Rev 1.4 07/04/2004 20:07:50 HHariri Updates for .NET Rev 1.3 10/19/2003 4:50:10 PM DSiders Added localization comments. Rev 1.2 10/12/2003 1:49:48 PM BGooijen Changed comment of last checkin Rev 1.1 10/12/2003 1:43:32 PM BGooijen Changed IdCompilerDefines.inc to Core\IdCompilerDefines.inc Rev 1.0 11/13/2002 07:54:34 AM JPMugaas } unit IdHTTPWebBrokerBridge; { Original Author: Dave Nottage. Modified by: Grahame Grieve Modified by: Chad Z. Hower (Kudzu) } interface {$i IdCompilerDefines.inc} uses Classes, HTTPApp, IdContext, IdCustomHTTPServer, IdException, IdTCPServer, IdIOHandlerSocket, {$IFDEF CLR}System.Text,{$ENDIF} WebBroker, WebReq; type EWBBException = class(EIdException); EWBBInvalidIdxGetDateVariable = class(EWBBException); EWBBInvalidIdxSetDateVariable = class(EWBBException ); EWBBInvalidIdxGetIntVariable = class(EWBBException ); EWBBInvalidIdxSetIntVariable = class(EWBBException ); EWBBInvalidIdxGetStrVariable = class(EWBBException); EWBBInvalidIdxSetStringVar = class(EWBBException); EWBBInvalidStringVar = class(EWBBException); TIdHTTPAppRequest = class(TWebRequest) protected FRequestInfo : TIdHTTPRequestInfo; FResponseInfo : TIdHTTPResponseInfo; FThread : TIdContext; FContentStream : TStream; FFreeContentStream : Boolean; // function GetDateVariable(Index: Integer): TDateTime; override; function GetIntegerVariable(Index: Integer): Integer; override; function GetStringVariable(Index: Integer): AnsiString; override; {$IFDEF VCL_XE_OR_ABOVE} function GetRemoteIP: string; override; function GetRawPathInfo: AnsiString; override; {$ENDIF} public constructor Create(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); destructor Destroy; override; function GetFieldByName(const Name: AnsiString): AnsiString; override; function ReadClient(var Buffer{$IFDEF CLR}: TBytes{$ENDIF}; Count: Integer): Integer; override; function ReadString(Count: Integer): AnsiString; override; {function ReadUnicodeString(Count: Integer): string;} function TranslateURI(const URI: string): string; override; function WriteClient(var ABuffer; ACount: Integer): Integer; override; {$IFDEF VCL_6_OR_ABOVE} {$DEFINE VCL_6_OR_ABOVE_OR_CLR} {$ENDIF} {$IFDEF CLR} {$DEFINE VCL_6_OR_ABOVE_OR_CLR} {$ENDIF} {$IFDEF VCL_6_OR_ABOVE_OR_CLR} function WriteHeaders(StatusCode: Integer; const ReasonString, Headers: AnsiString): Boolean; override; {$ENDIF} function WriteString(const AString: AnsiString): Boolean; override; end; TIdHTTPAppResponse = class(TWebResponse) protected FContent: string; FRequestInfo: TIdHTTPRequestInfo; FResponseInfo: TIdHTTPResponseInfo; FSent: Boolean; FThread: TIdContext; FContentType: AnsiString; // Workaround to preserve value of ContentType property // function GetContent: AnsiString; override; function GetDateVariable(Index: Integer): TDateTime; override; function GetStatusCode: Integer; override; function GetIntegerVariable(Index: Integer): Integer; override; function GetLogMessage: string; override; function GetStringVariable(Index: Integer): AnsiString; override; procedure SetContent(const AValue: AnsiString); override; procedure SetContentStream(AValue: TStream); override; procedure SetStatusCode(AValue: Integer); override; procedure SetStringVariable(Index: Integer; const Value: AnsiString); override; procedure SetDateVariable(Index: Integer; const Value: TDateTime); override; procedure SetIntegerVariable(Index: Integer; Value: Integer); override; procedure SetLogMessage(const Value: string); override; procedure MoveCookiesAndCustomHeaders; public constructor Create(AHTTPRequest: TWebRequest; AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); procedure SendRedirect(const URI: AnsiString); override; procedure SendResponse; override; procedure SendStream(AStream: TStream); override; function Sent: Boolean; override; end; TIdHTTPWebBrokerBridge = class(TIdCustomHTTPServer) private procedure RunWebModuleClass(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); protected FWebModuleClass: TComponentClass; // procedure DoCommandGet(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); override; procedure DoCommandOther(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); override; procedure InitComponent; override; public procedure RegisterWebModuleClass(AClass: TComponentClass); end; implementation uses IdResourceStringsProtocols, IdBuffer, IdHTTPHeaderInfo, IdGlobal, IdGlobalProtocols, IdCookie, IdStream, {$IFDEF STRING_IS_UNICODE}IdCharsets,{$ENDIF} SysUtils, Math {$IFDEF HAS_TNetEncoding} , System.NetEncoding {$ENDIF} ; type // Make HandleRequest accessible TWebDispatcherAccess = class(TCustomWebDispatcher); const INDEX_RESP_Version = 0; INDEX_RESP_ReasonString = 1; INDEX_RESP_Server = 2; INDEX_RESP_WWWAuthenticate = 3; INDEX_RESP_Realm = 4; INDEX_RESP_Allow = 5; INDEX_RESP_Location = 6; INDEX_RESP_ContentEncoding = 7; INDEX_RESP_ContentType = 8; INDEX_RESP_ContentVersion = 9; INDEX_RESP_DerivedFrom = 10; INDEX_RESP_Title = 11; // INDEX_RESP_ContentLength = 0; // INDEX_RESP_Date = 0; INDEX_RESP_Expires = 1; INDEX_RESP_LastModified = 2; // //Borland coder didn't define constants in HTTPApp INDEX_Method = 0; INDEX_ProtocolVersion = 1; INDEX_URL = 2; INDEX_Query = 3; INDEX_PathInfo = 4; INDEX_PathTranslated = 5; INDEX_CacheControl = 6; INDEX_Date = 7; INDEX_Accept = 8; INDEX_From = 9; INDEX_Host = 10; INDEX_IfModifiedSince = 11; INDEX_Referer = 12; INDEX_UserAgent = 13; INDEX_ContentEncoding = 14; INDEX_ContentType = 15; INDEX_ContentLength = 16; INDEX_ContentVersion = 17; INDEX_DerivedFrom = 18; INDEX_Expires = 19; INDEX_Title = 20; INDEX_RemoteAddr = 21; INDEX_RemoteHost = 22; INDEX_ScriptName = 23; INDEX_ServerPort = 24; INDEX_Content = 25; INDEX_Connection = 26; INDEX_Cookie = 27; INDEX_Authorization = 28; { TIdHTTPAppRequest } constructor TIdHTTPAppRequest.Create(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var i: Integer; begin FThread := AThread; FRequestInfo := ARequestInfo; FResponseInfo := AResponseInfo; inherited Create; for i := 0 to ARequestInfo.Cookies.Count - 1 do begin CookieFields.Add(ARequestInfo.Cookies[i].ClientCookie); end; if Assigned(FRequestInfo.PostStream) then begin FContentStream := FRequestInfo.PostStream; FFreeContentStream := False; end else begin if FRequestInfo.FormParams <> '' then begin {do not localize} // an input form that was submitted as "application/www-url-encoded"... FContentStream := TStringStream.Create(FRequestInfo.FormParams); end else begin // anything else for now... FContentStream := TStringStream.Create(FRequestInfo.UnparsedParams); end; FFreeContentStream := True; end; end; destructor TIdHTTPAppRequest.Destroy; begin if FFreeContentStream then begin FreeAndNil(FContentStream); end; inherited; end; function TIdHTTPAppRequest.GetDateVariable(Index: Integer): TDateTime; var LValue: string; begin LValue := string(GetStringVariable(Index)); if Length(LValue) > 0 then begin Result := ParseDate(LValue); end else begin Result := -1; end; end; function TIdHTTPAppRequest.GetIntegerVariable(Index: Integer): Integer; begin Result := StrToIntDef(string(GetStringVariable(Index)), -1) end; {$IFDEF VCL_XE_OR_ABOVE} function TIdHTTPAppRequest.GetRawPathInfo: AnsiString; begin Result := AnsiString(FRequestInfo.URI); end; function TIdHTTPAppRequest.GetRemoteIP: string; begin Result := String(FRequestInfo.RemoteIP); end; {$ENDIF} function TIdHTTPAppRequest.GetStringVariable(Index: Integer): AnsiString; var s: string; LPos: TIdStreamSize; LBytes: TIdBytes; begin LBytes := nil; case Index of INDEX_Method : Result := AnsiString(FRequestInfo.Command); INDEX_ProtocolVersion : Result := AnsiString(FRequestInfo.Version); //INDEX_URL : Result := AnsiString(FRequestInfo.Document); INDEX_URL : Result := AnsiString(''); // Root - consistent with ISAPI which return path to root INDEX_Query : Result := AnsiString(FRequestInfo.QueryParams); INDEX_PathInfo : Result := AnsiString(FRequestInfo.Document); INDEX_PathTranslated : Result := AnsiString(FRequestInfo.Document); // it's not clear quite what should be done here - we can't translate to a path INDEX_CacheControl : Result := GetFieldByName('Cache-Control'); {do not localize} INDEX_Date : Result := GetFieldByName('Date'); {do not localize} INDEX_Accept : Result := AnsiString(FRequestInfo.Accept); INDEX_From : Result := AnsiString(FRequestInfo.From); INDEX_Host: begin s := FRequestInfo.Host; Result := AnsiString(Fetch(s, ':')); end; INDEX_IfModifiedSince : Result := GetFieldByName('If-Modified-Since'); {do not localize} INDEX_Referer : Result := AnsiString(FRequestInfo.Referer); INDEX_UserAgent : Result := AnsiString(FRequestInfo.UserAgent); INDEX_ContentEncoding : Result := AnsiString(FRequestInfo.ContentEncoding); INDEX_ContentType : Result := AnsiString(FRequestInfo.ContentType); INDEX_ContentLength : Result := AnsiString(IntToStr(FContentStream.Size)); INDEX_ContentVersion : Result := GetFieldByName('CONTENT_VERSION'); {do not localize} INDEX_DerivedFrom : Result := GetFieldByName('Derived-From'); {do not localize} INDEX_Expires : Result := GetFieldByName('Expires'); {do not localize} INDEX_Title : Result := GetFieldByName('Title'); {do not localize} INDEX_RemoteAddr : Result := AnsiString(FRequestInfo.RemoteIP); INDEX_RemoteHost : Result := GetFieldByName('REMOTE_HOST'); {do not localize} INDEX_ScriptName : Result := ''; INDEX_ServerPort: begin s := FRequestInfo.Host; Fetch(s, ':'); if Length(s) = 0 then begin s := IntToStr(FThread.Connection.Socket.Binding.Port); // Result := '80'; end; Result := AnsiString(s); end; INDEX_Content: begin if FFreeContentStream then begin Result := AnsiString(TStringStream(FContentStream).DataString); end else begin LPos := FContentStream.Position; FContentStream.Position := 0; try // RLebeau 2/21/2009: not using ReadStringAsCharSet() anymore. Since // this method returns an AnsiString, the stream data should not be // decoded to Unicode and then converted to Ansi. That can lose // characters. Also, for D2009+, the AnsiString payload should have // the proper codepage assigned to it as well so it can be converted // correctly if assigned to other string variables later on... // Result := ReadStringAsCharSet(FContentStream, FRequestInfo.CharSet); TIdStreamHelper.ReadBytes(FContentStream, LBytes); {$IFDEF DOTNET} // RLebeau: how to handle this correctly in .NET? Result := AnsiString(BytesToStringRaw(LBytes)); {$ELSE} SetString(Result, PAnsiChar(LBytes), Length(LBytes)); {$IFDEF VCL_2009_OR_ABOVE} SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FRequestInfo.CharSet), False); {$ENDIF} {$ENDIF} finally FContentStream.Position := LPos; end; end; end; INDEX_Connection : Result := GetFieldByName('Connection'); {do not localize} INDEX_Cookie : Result := ''; // not available at present. FRequestInfo.Cookies....; INDEX_Authorization : Result := GetFieldByName('Authorization'); {do not localize} else Result := ''; end; end; function TIdHTTPAppRequest.GetFieldByName(const Name: AnsiString): AnsiString; begin Result := AnsiString(FRequestInfo.RawHeaders.Values[string(Name)]); end; function TIdHTTPAppRequest.ReadClient(var Buffer{$IFDEF CLR}: TBytes{$ENDIF}; Count: Integer): Integer; begin {$IFDEF CLR} Result := TIdStreamHelper.ReadBytes(FContentStream, Buffer, Count); {$ELSE} Result := FContentStream.Read(Buffer, Count); {$ENDIF} // well, it shouldn't be less than 0. but let's not take chances if Result < 0 then begin Result := 0; end; end; function TIdHTTPAppRequest.ReadString(Count: Integer): AnsiString; var LBytes: TIdBytes; begin // RLebeau 2/21/2009: not using ReadStringAsCharSet() anymore. Since // this method returns an AnsiString, the stream data should not be // decoded to Unicode and then converted to Ansi. That can lose // characters. // Result := AnsiString(ReadStringFromStream(FContentStream, Count)); LBytes := nil; TIdStreamHelper.ReadBytes(FContentStream, LBytes, Count); {$IFDEF DOTNET} // RLebeau: how to handle this correctly in .NET? Result := AnsiString(BytesToStringRaw(LBytes)); {$ELSE} SetString(Result, PAnsiChar(LBytes), Length(LBytes)); {$IFDEF VCL_2009_OR_ABOVE} SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FRequestInfo.CharSet), False); {$ENDIF} {$ENDIF} end; function TIdHTTPAppRequest.TranslateURI(const URI: string): string; begin // we don't have the concept of a path translation. It's not quite clear // what to do about this. Comments welcome (grahame@kestral.com.au) Result := URI; end; {$IFDEF VCL_6_OR_ABOVE_OR_CLR} function TIdHTTPAppRequest.WriteHeaders(StatusCode: Integer; const ReasonString, Headers: AnsiString): Boolean; begin FResponseInfo.ResponseNo := StatusCode; FResponseInfo.ResponseText := string(ReasonString); FResponseInfo.CustomHeaders.Add(string(Headers)); FResponseInfo.WriteHeader; Result := True; end; {$ENDIF} function TIdHTTPAppRequest.WriteString(const AString: AnsiString): Boolean; begin FThread.Connection.IOHandler.Write(string(AString)); Result := True; end; function TIdHTTPAppRequest.WriteClient(var ABuffer; ACount: Integer): Integer; var LBuffer: TIdBytes; begin SetLength(LBuffer, ACount); {$IFNDEF CLR} Move(ABuffer, LBuffer[0], ACount); {$ELSE} // RLebeau: this can't be right? It is interpretting the source as a // null-terminated character string, which is likely not the case... CopyTIdBytes(ToBytes(string(ABuffer)), 0, LBuffer, 0, ACount); {$ENDIF} FThread.Connection.IOHandler.Write(LBuffer); Result := ACount; end; { TIdHTTPAppResponse } constructor TIdHTTPAppResponse.Create(AHTTPRequest: TWebRequest; AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin FThread := AThread; FRequestInfo := ARequestInfo; FResponseInfo := AResponseInfo; inherited Create(AHTTPRequest); if Length(FHTTPRequest.ProtocolVersion) = 0 then begin Version := '1.0'; {do not localize} end; StatusCode := 200; LastModified := -1; Expires := -1; Date := -1; // RLebeau 8/13/2015: no longer setting a default ContentType here. Doing so // sets a default CharSet, which would get carried over if the user assigns a // new *non-text* ContentType without an explicit charset. TAppResponse does // not expose access to the FResponseInfo.CharSet property. For example, if // the user sets TAppResponse.ContentType to 'image/jpeg', the resulting // Content-Type header woud be 'image/jpeg; charset=ISO-8859-1', which can // cause problems for some clients. Besides, TIdHTTPResponseInfo.WriteHeader() // sets the ContentType to 'text/html; charset=ISO-8859-1' if no ContentType // has been provided but there is ContentText/ContentStream data, so this is // redundant here anyway... // // ContentType := 'text/html'; {do not localize} end; function TIdHTTPAppResponse.GetContent: AnsiString; {$IFDEF STRING_IS_UNICODE} var LEncoding: IIdTextEncoding; LBytes: TIdBytes; {$ENDIF} begin {$IFDEF STRING_IS_UNICODE} // RLebeau 2/21/2009: encode the content using the specified charset. // Also, the AnsiString payload should have the proper codepage assigned // to it as well so it can be converted correctly if assigned to other // string variables later on... Result := ''; LEncoding := CharsetToEncoding(FResponseInfo.CharSet); LBytes := LEncoding.GetBytes(FResponseInfo.ContentText); {$IFDEF DOTNET} // RLebeau: how to handle this correctly in .NET? Result := AnsiString(BytesToStringRaw(LBytes)); {$ELSE} SetString(Result, PAnsiChar(LBytes), Length(LBytes)); {$IFDEF VCL_2009_OR_ABOVE} SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FResponseInfo.CharSet), False); {$ENDIF} {$ENDIF} {$ELSE} Result := FResponseInfo.ContentText; {$ENDIF} end; function TIdHTTPAppResponse.GetLogMessage: string; begin Result := ''; end; function TIdHTTPAppResponse.GetStatusCode: Integer; begin Result := FResponseInfo.ResponseNo; end; function TIdHTTPAppResponse.GetDateVariable(Index: Integer): TDateTime; // WebBroker apps are responsible for conversion to GMT, Indy HTTP server expects apps to pas local time function ToGMT(ADateTime: TDateTime): TDateTime; begin Result := ADateTime; if Result <> -1 then Result := Result - OffsetFromUTC; end; begin //TODO: resource string these case Index of INDEX_RESP_Date : Result := ToGMT(FResponseInfo.Date); INDEX_RESP_Expires : Result := ToGMT(FResponseInfo.Expires); INDEX_RESP_LastModified : Result := ToGMT(FResponseInfo.LastModified); else raise EWBBInvalidIdxGetDateVariable.Create( Format( RSWBBInvalidIdxGetDateVariable,[inttostr(Index)])); end; end; procedure TIdHTTPAppResponse.SetDateVariable(Index: Integer; const Value: TDateTime); // WebBroker apps are responsible for conversion to GMT, Indy HTTP server expects apps to pas local time function ToLocal(ADateTime: TDateTime): TDateTime; begin Result := ADateTime; if Result <> -1 then Result := Result + OffsetFromUTC; end; begin //TODO: resource string these case Index of INDEX_RESP_Date : FResponseInfo.Date := ToLocal(Value); INDEX_RESP_Expires : FResponseInfo.Expires := ToLocal(Value); INDEX_RESP_LastModified : FResponseInfo.LastModified := ToLocal(Value); else raise EWBBInvalidIdxSetDateVariable.Create(Format(RSWBBInvalidIdxSetDateVariable,[inttostr(Index) ])); end; end; function TIdHTTPAppResponse.GetIntegerVariable(Index: Integer): Integer; begin //TODO: resource string these case Index of INDEX_RESP_ContentLength: Result := FResponseInfo.ContentLength; else raise EWBBInvalidIdxGetIntVariable.Create( Format( RSWBBInvalidIdxGetIntVariable,[inttostr(Index)])); end; end; procedure TIdHTTPAppResponse.SetIntegerVariable(Index, Value: Integer); begin //TODO: resource string these case Index of INDEX_RESP_ContentLength: FResponseInfo.ContentLength := Value; else raise EWBBInvalidIdxSetIntVariable.Create( Format(RSWBBInvalidIdxSetIntVariable,[inttostr(Index)])); {do not localize} end; end; function TIdHTTPAppResponse.GetStringVariable(Index: Integer): AnsiString; begin //TODO: resource string these case Index of INDEX_RESP_Version :Result := AnsiString(FRequestInfo.Version); INDEX_RESP_ReasonString :Result := AnsiString(FResponseInfo.ResponseText); INDEX_RESP_Server :Result := AnsiString(FResponseInfo.Server); INDEX_RESP_WWWAuthenticate :Result := AnsiString(FResponseInfo.WWWAuthenticate.Text); INDEX_RESP_Realm :Result := AnsiString(FResponseInfo.AuthRealm); INDEX_RESP_Allow :Result := AnsiString(FResponseInfo.CustomHeaders.Values['Allow']); {do not localize} INDEX_RESP_Location :Result := AnsiString(FResponseInfo.Location); INDEX_RESP_ContentEncoding :Result := AnsiString(FResponseInfo.ContentEncoding); INDEX_RESP_ContentType : begin if FContentType <> '' then begin Result := FContentType; end else begin Result := AnsiString(FResponseInfo.ContentType); end; end; INDEX_RESP_ContentVersion :Result := AnsiString(FResponseInfo.ContentVersion); INDEX_RESP_DerivedFrom :Result := AnsiString(FResponseInfo.CustomHeaders.Values['Derived-From']); {do not localize} INDEX_RESP_Title :Result := AnsiString(FResponseInfo.CustomHeaders.Values['Title']); {do not localize} else raise EWBBInvalidIdxGetStrVariable.Create(Format(RSWBBInvalidIdxGetStrVariable,[ IntToStr(Index)])); end; end; procedure TIdHTTPAppResponse.SetStringVariable(Index: Integer; const Value: AnsiString); begin //TODO: resource string these case Index of INDEX_RESP_Version :EWBBInvalidStringVar.Create(RSWBBInvalidStringVar); INDEX_RESP_ReasonString :FResponseInfo.ResponseText := string(Value); INDEX_RESP_Server :FResponseInfo.Server := string(Value); INDEX_RESP_WWWAuthenticate :FResponseInfo.WWWAuthenticate.Text := string(Value); INDEX_RESP_Realm :FResponseInfo.AuthRealm := string(Value); INDEX_RESP_Allow :FResponseInfo.CustomHeaders.Values['Allow'] := string(Value); {do not localize} INDEX_RESP_Location :FResponseInfo.Location := string(Value); INDEX_RESP_ContentEncoding :FResponseInfo.ContentEncoding := string(Value); INDEX_RESP_ContentType : begin FResponseInfo.ContentType := string(Value); FContentType := Value; end; INDEX_RESP_ContentVersion :FResponseInfo.ContentVersion := string(Value); INDEX_RESP_DerivedFrom :FResponseInfo.CustomHeaders.Values['Derived-From'] := string(Value); {do not localize} INDEX_RESP_Title :FResponseInfo.CustomHeaders.Values['Title'] := string(Value); {do not localize} else raise EWBBInvalidIdxSetStringVar.Create( Format(RSWBBInvalidIdxSetStringVar,[IntToStr(Index)])); {do not localize} end; end; procedure TIdHTTPAppResponse.SendRedirect(const URI: AnsiString); begin FSent := True; MoveCookiesAndCustomHeaders; FResponseInfo.Redirect(string(URI)); end; procedure TIdHTTPAppResponse.SendResponse; begin FSent := True; // Reset to -1 so Indy will auto set it FResponseInfo.ContentLength := -1; MoveCookiesAndCustomHeaders; FResponseInfo.WriteContent; end; procedure TIdHTTPAppResponse.SendStream(AStream: TStream); begin FThread.Connection.IOHandler.Write(AStream); end; function TIdHTTPAppResponse.Sent: Boolean; begin Result := FSent; end; procedure TIdHTTPAppResponse.SetContent(const AValue: AnsiString); var LValue : string; begin {$IFDEF STRING_IS_UNICODE} // RLebeau 3/28/2013: decode the content using the specified charset. if FResponseInfo.CharSet <> '' then begin // AValue contains Encoded bytes if AValue <> '' then begin LValue := CharsetToEncoding(FResponseInfo.CharSet).GetString(RawToBytes(PAnsiChar(AValue)^, Length(AValue))); end; end else begin LValue := string(AValue); end; {$ELSE} LValue := string(AValue); {$ENDIF} FResponseInfo.ContentText := LValue; FResponseInfo.ContentLength := Length(LValue); end; procedure TIdHTTPAppResponse.SetLogMessage(const Value: string); begin // logging not supported end; procedure TIdHTTPAppResponse.SetStatusCode(AValue: Integer); begin FResponseInfo.ResponseNo := AValue; end; procedure TIdHTTPAppResponse.SetContentStream(AValue: TStream); begin inherited; FResponseInfo.ContentStream := AValue; end; function DoHTTPEncode(const AStr: AnsiString): String; begin {$IFDEF HAS_TNetEncoding} Result := TNetEncoding.URL.Encode(string(AStr)); {$ELSE} Result := String(HTTPEncode(AStr)); {$ENDIF} end; procedure TIdHTTPAppResponse.MoveCookiesAndCustomHeaders; var i: Integer; LSrcCookie: TCookie; LDestCookie: TIdCookie; begin for i := 0 to Cookies.Count - 1 do begin LSrcCookie := Cookies[i]; LDestCookie := FResponseInfo.Cookies.Add; LDestCookie.CookieName := DoHTTPEncode(LSrcCookie.Name); LDestCookie.Value := DoHTTPEncode(LSrcCookie.Value); LDestCookie.Domain := String(LSrcCookie.Domain); LDestCookie.Path := String(LSrcCookie.Path); LDestCookie.Expires := LSrcCookie.Expires; LDestCookie.Secure := LSrcCookie.Secure; // TODO: LDestCookie.HttpOnly := LSrcCookie.HttpOnly; end; FResponseInfo.CustomHeaders.Clear; FResponseInfo.CustomHeaders.AddStdValues(CustomHeaders); end; { TIdHTTPWebBrokerBridge } procedure TIdHTTPWebBrokerBridge.DoCommandOther(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin DoCommandGet(AThread, ARequestInfo, AResponseInfo); end; procedure TIdHTTPWebBrokerBridge.InitComponent; begin inherited InitComponent; // FOkToProcessCommand := True; end; type TIdHTTPWebBrokerBridgeRequestHandler = class(TWebRequestHandler) {$IFDEF HAS_CLASSVARS} private class var FWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler; {$ENDIF} public constructor Create(AOwner: TComponent); override; {$IFDEF HAS_CLASSVARS} {$IFDEF HAS_CLASSDESTRUCTOR} class destructor Destroy; {$ENDIF} {$ENDIF} destructor Destroy; override; procedure Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); end; {$IFNDEF HAS_CLASSVARS} var IndyWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler = nil; {$ENDIF} { TIdHTTPWebBrokerBridgeRequestHandler } procedure TIdHTTPWebBrokerBridgeRequestHandler.Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var LRequest: TIdHTTPAppRequest; LResponse: TIdHTTPAppResponse; begin try LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo); try LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo); try // WebBroker will free it and we cannot change this behaviour AResponseInfo.FreeContentStream := False; HandleRequest(LRequest, LResponse); finally FreeAndNil(LResponse); end; finally FreeAndNil(LRequest); end; except // Let Indy handle this exception raise; end; end; constructor TIdHTTPWebBrokerBridgeRequestHandler.Create(AOwner: TComponent); begin inherited; Classes.ApplicationHandleException := HandleException; end; destructor TIdHTTPWebBrokerBridgeRequestHandler.Destroy; begin Classes.ApplicationHandleException := nil; inherited; end; {$IFDEF HAS_CLASSVARS} {$IFDEF HAS_CLASSDESTRUCTOR} class destructor TIdHTTPWebBrokerBridgeRequestHandler.Destroy; begin FreeAndNil(FWebRequestHandler); end; {$ENDIF} {$ENDIF} function IdHTTPWebBrokerBridgeRequestHandler: TWebRequestHandler; begin {$IFDEF HAS_CLASSVARS} if not Assigned(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler) then TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil); Result := TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler; {$ELSE} if not Assigned(IndyWebRequestHandler) then IndyWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil); Result := IndyWebRequestHandler; {$ENDIF} end; procedure TIdHTTPWebBrokerBridge.DoCommandGet(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); begin if FWebModuleClass <> nil then begin // FWebModuleClass, RegisterWebModuleClass supported for backward compatability RunWebModuleClass(AThread, ARequestInfo, AResponseInfo) end else begin {$IFDEF HAS_CLASSVARS} TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo); {$ELSE} IndyWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo); {$ENDIF} end; end; procedure TIdHTTPWebBrokerBridge.RunWebModuleClass(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var LRequest: TIdHTTPAppRequest; LResponse: TIdHTTPAppResponse; LWebModule: TCustomWebDispatcher; {$IFDEF VCL_6_OR_ABOVE} WebRequestHandler: IWebRequestHandler; {$ENDIF} Handled: Boolean; begin LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo); try LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo); try // WebBroker will free it and we cannot change this behaviour AResponseInfo.FreeContentStream := False; // There are better ways in D6, but this works in D5 LWebModule := FWebModuleClass.Create(nil) as TCustomWebDispatcher; try {$IFDEF VCL_6_OR_ABOVE} if Supports(LWebModule, IWebRequestHandler, WebRequestHandler) then begin try Handled := WebRequestHandler.HandleRequest(LRequest, LResponse); finally WebRequestHandler := nil; end; end else begin Handled := TWebDispatcherAccess(LWebModule).DispatchAction(LRequest, LResponse); end; {$ELSE} Handled := TWebDispatcherAccess(LWebModule).DispatchAction(LRequest, LResponse); {$ENDIF} if Handled and (not LResponse.Sent) then begin LResponse.SendResponse; end; finally FreeAndNil(LWebModule); end; finally FreeAndNil(LResponse); end; finally FreeAndNil(LRequest); end; end; // FWebModuleClass, RegisterWebModuleClass supported for backward compatability // Instead set WebModuleClass using: WebReq.WebRequestHandler.WebModuleClass := TWebModule1; procedure TIdHTTPWebBrokerBridge.RegisterWebModuleClass(AClass: TComponentClass); begin FWebModuleClass := AClass; end; initialization WebReq.WebRequestHandlerProc := IdHTTPWebBrokerBridgeRequestHandler; {$IFDEF HAS_CLASSVARS} {$IFNDEF HAS_CLASSDESTRUCTOR} finalization FreeAndNil(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler); {$ENDIF} {$ELSE} finalization FreeAndNil(IndyWebRequestHandler); {$ENDIF} end.