views:

180

answers:

5

I am having trouble implementing a HTTP server with indy 10 in delphi 2007.

I have set up a simple event handler for the CommandGet event.

When replying to data sent using the GET method I can parse the params and send XML data back with no problems. (see code below)

    Download := ARequestInfo.Params.Values['dld'];
    Config := ARequestInfo.Params.Values['config'];
    Flash := ARequestInfo.Params.Values['flash'];
    Employees := ARequestInfo.Params.Values['employees'];
    Schedule := ARequestInfo.Params.Values['schedules'];
    AppTables := ARequestInfo.Params.Values['apptables'];

    Heartbeat :=  NewHeartbeat;

    Heartbeat.Version.Dld := Download;
    Heartbeat.Version.Config := Config;
    Heartbeat.Version.Flash := Flash;
    Heartbeat.Version.Employee := Employees;
    Heartbeat.Version.Schedule := Schedule;
    Heartbeat.Version.AppTables := AppTables;

    AResponseInfo.ContentType := 'application/xml';
    AResponseInfo.ResponseNo := 200;
    AResponseInfo.ContentText := '<?xml version="1.0" encoding="utf-8"?>' +
      #13+#10 + FormatXMLData(Heartbeat.XML);

When I try to reply to the data that is sent using a POST the response is never sent by indy, instead a EidConnClosedGracefully is raised by TIdIOHandler.WriteDirect form the CheckForDisconnect(True, True) line. here is how i'm handling the incoming POST data

    XMLDocument1.xml.Clear;
    XMLDocument1.Active := True;
    XMLDocument1.XML.text := ARequestInfo.FormParams;

    SynMemo1.Lines.Add(ARequestInfo.FormParams);
    SynMemo1.Lines.Add(#13+#10);

    if XMLDocument1.XML.Count > 0 then
    begin
      XMLDocument1.XML.Delete(0);
      XMLDocument1.XML.Delete(0);

      for i := pred(xmldocument1.xml.count) downto 0 do
      begin
        stmp := XMLDocument1.XML.Strings[i];

        if Length(stmp) > 0 then
        begin
          if Copy(stmp,1,1) = '<' then
            break
          else
            XMLDocument1.XML.Delete(i);
        end
        else
          XMLDocument1.XML.Delete(i);

      end;

      XMLDocument1.XML.Text := StringReplace(XMLDocument1.XML.Text,
        '<Punches ', '<Punches xmlns="http://ats/punch" ', [rfReplaceAll]);



    end;

    Punch := GetPunches(XMLDocument1);

    PunchReply := NewOperationStatus;
    PunchReply.Uid := Punch.Uid;
    PunchReply.NodeValue := 'OK';

    stmp := '<?xml version="1.0" encoding="utf-8"?>' +
      #13+#10 + FormatXMLData(PunchReply.XML);
    SynMemo1.Lines.Add(stmp);
    SynMemo1.Lines.Add(#13+#10);

    AResponseInfo.ContentType := 'text/html';
    AResponseInfo.ContentStream := TStringStream.Create(stmp);

I have used wireshark to see what is happening and it looks like indy is sending an ACK back before the response is sent and causing the client to disconnect.

to test this I set-up Apache with PHP and wrote a PHP script to do the same job and everything works ok, The difference is the POST data is replied to with the response content not an ACK.

any suggestions on how to resolve this so I respond to the POST data as well as GET.

A: 

ACKs are not sent by Indy itself, but by the underlying socket instead, and even then only in reply to packets received from the other party. EIdConnClosedGracefully means that the client is intentionally disconnecting the socket on its end before your server can sent its reply data. The fact that an ACK is present helps prove that (the socket is likely ACK'ing the client's FIN packet during disconnection).

Remy Lebeau - TeamB
This is exactly what I was thinking until I set-up apache and php and every thing worked ok. Also worth mentioning is I can browse to the page ok. In wire shark I get the normal SYN, SYN ACK, ACK, then the data is posted then ACK then FIN, ACK, FIN, ACK. But in apache between the data post and ACK the reply is sent. It looks like the ack is triggering the device to disconnect
MikeT
SYN/SYN+ACK/ACK is the connection handshake. FIN/ACK/FIN/ACK is a negotiated disconnect. I assure you that an ACK alone is not causing the device to disconnect. Any server, whether it be TIdHTTPServer ot Apache, is going to ACK the device's HTTP request data. The device is simply not waiting for your TIdHTTPServer reply to arrive before disconnecting. Without seeing the actual Wireshark traces, my first guess would be maybe a reading timeout is occuring on the device. Does you TIdHTTPServer take longer to reply than the Apache server does?
Remy Lebeau - TeamB
Sorry maybe I not making myself clear. I don't think the ack is causing a disconnect but siganling to the device that the device should initiate the disconnect handshake. My thought was also a timeout issue looking at the wireshark traces this cold make sense but I would have expected tidhttpserver to reply in under the 5000 ms timeout. Considering the heartbeat reply in the get method is a more complex reply than from the post data. I don't understand what could be taking so long.
MikeT
Receiving an ACK will NOT trigger a disconnect. The device's logic has to be handling the disconnect separately.
Remy Lebeau - TeamB
A: 

I'm stuck with this now, as you can see from this wireshark trace (click link for image) i have increased the timeout to 20 seconds. and its still not working. I'm going to do some more investigations. and see what I can find out. Its looking like Indy thinks the disconnect has occurred before it has

Click here for image

MikeT
A: 

Its looking like I may have tracked this one down. The device is not sending the Content-Length in the http header so when TidIOHandler.ReadStream is called it is setting AReadUtillDisconnect to True, hence disconnecting before I can send a reply. Hope this makes sense.

MikeT
The only legitimate reason not to include a `Content-Length` header on a POST request is if `Transfer-Encoding: chunked` is being used instead. TIdHTTPServer in D2007 did not support chunked requests. You need to upgrade to the current Indy 10.5.7 snapshot, as support for chunked requests was added just a few months ago.
Remy Lebeau - TeamB
A: 

Looks like the http is being sent as

Transfer-Encoding: chunked

Therefore there is no content-length. but the version of Indy I am using doesn't support this mode.

MikeT
You need to upgrade to the latest Indy 10.5.7 snapshot in order to support chunked requests.
Remy Lebeau - TeamB
A: 

Ok, I have this working now. I have added some code to the idCustomHTTPServer to support chuncked Transfer-Encoding. Below is a procedure I nested in the TIdCustomHTTPServer.DoExecute function.

procedure ProcessChunckedData(AContext: TIdContext; var VPostStream: TStream);
var
  ChunkSize: Integer;
begin
  repeat
    ChunkSize := StrToIntDef('$' + AContext.Connection.IOHandler.ReadLn, 0);
    if ChunkSize > 0 then
    begin
      AContext.Connection.IOHandler.ReadStream(VPostStream, ChunkSize, False);
    end;
  until ChunkSize = 0;
end;

And here is how its being called.

if LRequestInfo.RawHeaders.Values['Transfer-Encoding'] = 'chunked' then
  ProcessChunckedData(AContext, LRequestInfo.FPostStream)
else
begin
  if LRequestInfo.ContentLength > 0 then begin
    IOHandler.ReadStream(LRequestInfo.PostStream, LRequestInfo.ContentLength);
  end else if LRequestInfo.CommandType = hcPOST then begin
    if not LRequestInfo.HasContentLength then begin
      IOHandler.ReadStream(LRequestInfo.PostStream, -1, True);
    end;
  end;
end;

I'm going to do some final testing and see if this is working ok. and maybe one day I will see this officially supported in Indy.

MikeT
OK i just checked out the latest source and can see that the work has been done recently
MikeT