Search code examples
delphiindytcpserver

Delphi TIdTcpServer get POST parameters sent by browser


I am developing a simple Web Server to communicate with my Web App which runs in a browser.

I managed to create a IdTcpServer and get the RequestHeaders submitted by the browser. I don't know how it's possible to get POST parameters of the form.

here is my code so far:

procedure TMyServer.WebServerExecute(AContext: TIdContext);
var
    RawData: String;
    TRequestHeader: TStringList;
begin
    TRequestHeader := TStringList.Create;

    // GET Request Headers
    RawData := AContext.Connection.Socket.ReadLn(IndyTextEncoding_UTF8);
    while RawData <> '' do
      begin
        TRequestHeader.Add(RawData);
        RawData := AContext.Connection.Socket.ReadLn(IndyTextEncoding_UTF8);
      end;

    // How To get POST or GET Parameters of the HTML form?
    // ...

    // Respond to the Client
    Respond(...);

    TRequestHeader.Free;
end;

Solution

  • Once you have the request headers, you need to analyze them to know whether a message body even exists, and in what format it is encoded in so you can read it properly (see RFC 2616 section 4.4), eg:

    procedure TMyServer.WebServerConnect(AContext: TIdContext);
    begin
      AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
    end;
    
    procedure TMyServer.WebServerExecute(AContext: TIdContext);
    var
      ReqLine, Value: String;
      I: Integer;
      Size: Int64;
      TRequestHeader: TIdHeaderList;
    begin
      TRequestHeader := TIdHeaderList.Create;
      try
        // GET Request Line ...
        ReqLine := AContext.Connection.IOHandler.ReadLn;
    
        // TODO: parse ReqLine as needed to extract HTTP version, resource, and query params ...
    
        // GET Request Headers ...
        repeat
          Value := AContext.Connection.IOHandler.ReadLn;
          if Value = '' then Break;
          TRequestHeader.Add(Value);
        until False;
    
        // alternatively:
        // AContext.Connection.IOHandler.Capture(TRequestHeader, '', False);
    
        // get POST or GET data ...
    
        Value := TRequestHeader.Values['Transfer-Encoding'];
        if (Value <> '') and (not TextIsSame(Value, 'identity')) then
        begin
          repeat
            Value := AContext.Connection.IOHandler.ReadLn;
    
            I := Pos(';', Value);
            if I > 0 then begin
              Value := Copy(Value, 1, I - 1);
            end;
    
            Size := StrToInt64('$' + Trim(S));
            if Size = 0 then begin
              Break;
            end;
    
            // read specified number of bytes as needed ...
    
            AContext.Connection.IOHandler.ReadLn; // read CRLF at end of chunk data
          until False;
    
          // read trailer headers
          repeat
            Value := AContext.Connection.IOHandler.ReadLn;
            if (Value = '') then Break;
            // update TRequestHeader as needed...
          until False;  
        end
        else
        begin
          Value := TRequestHeader.Values['Content-Length'];
          if Value = '' then
          begin
            // respond with 411 error
            Exit;
          end;
    
          Size := StrToInt64(Value);
    
          // read specified number of bytes as needed ...
        end;
    
        // process request as needed, based on the value of
        // ReqLine, TRequestHeader.Values['Content-Type'], etc ...
    
        // Respond to the Client
        Respond(...);
      finally
        TRequestHeader.Free;
      end;
    end;
    

    That being said, Indy has a TIdHTTPServer component that handles the hard work of implementing the HTTP protocol for you (which is not as trivial a task as you think it is). You should not be using TIdTCPServer for this.

    You can assign a handler to the TIdHTTPServer.OnCommandGet event and use the provided ARequestInfo and AResponseInfo parameters as needed. The request headers will be in the ARequestInfo.RawHeaders property, and various sub-properties (ARequestInfo.ContentType, ARequestInfo.ContentLength, etc). The GET/POST data will be in the ARequestInfo.QueryParams, ARequestInfo.FormParams, and ARequestInfo.PostStream properties accordingly, eg:

    procedure TMyServer.WebServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    begin
      // use ARequestInfo.RawHeaders and ARequestInfo.QueryParams as needed ...
    
      if ARequestInfo.CommandType = hcPOST then
      begin
        if IsHeaderMediaType(ARequestInfo.ContentType, 'application/x-www-form-urlencoded') then
        begin
          // use ARequestInfo.FormParams as needed ...
        end
        else begin
          // use ARequestInfo.PostStream as needed ...
        end;
      end else
      begin
        // process GET/HEAD requests as needed ...
      end;
    
      // Respond to the Client, by populating AResponseInfo as needed ...
    end;