RTC Forums
May 12, 2024, 02:13:18 AM *
Welcome, Guest. Please login or register.

Login with username, password and session length
 
   Home   Help Login Register  
Pages: [1]
  Print  
Author Topic: multipart/form-data function  (Read 3425 times)
Dany
RTC License++
*****
Posts: 69


« on: November 06, 2014, 01:45:56 PM »

Hi!

I have written a small unit to help me post data to servers expecting a HTML form. Shown below, (i have stripped away some very specific stuff so it might not compile), is the general idea and it seems to work pretty well.

If anyone cares to comment about this - i have a feeling it can be improved - i'd appreciate it. Especially how i have used the various RTC data types and Info objects...

Code:
type
  TClientUtilsEventClass = class
  private
    class procedure PostMultipartBeginRequest(Sender: TRtcConnection);
    class procedure PostMultipartDataSent(Sender: TRtcConnection);
    class procedure PostMultipartDataReceived(Sender: TRtcConnection);
  end;

const
  CR = #$0d;
  LF = #$0a;
  CRLF = CR + LF;
  C64k = 65536;

class procedure TClientUtilsEventClass.PostMultipartBeginRequest(Sender: TRtcConnection);
var
  Cli: TRtcDataClient absolute Sender;
begin
  Cli.WriteHeader;
end;

class procedure TClientUtilsEventClass.PostMultipartDataSent(Sender: TRtcConnection);
var
  lSize: int64;
  Cli: TRtcDataClient absolute Sender;
begin
  if Cli.Request.ContentLength > Cli.Request.ContentOut then
  begin
    lSize := Cli.Request.ContentLength - Cli.Request.ContentOut;
    if lSize > C64k then lSize := C64k;
    Cli.Write(RtcBytesToString(Copy(Cli.Request.Info.asByteArray['payload'], Cli.Request.ContentOut, lSize)));
  end;
end;

class procedure TClientUtilsEventClass.PostMultipartDataReceived(Sender: TRtcConnection);
var
  Cli: TRtcDataClient absolute Sender;
begin
  if Cli.Response.Done then
    Cli.Info['response'] := Cli.Read;
end;

function PostHTTPMultipart(const aCommand: string; lAttachments: TStrings): Variant;

  procedure AddFile(aPayload: TRtcHugeString; aBound: RtcString; aFileName: string);
  begin
    aPayload.Add('--' + aBound + CRLF);
    aPayload.Add(Utf8Encode('content-disposition: form-data; name="datafile"; filename="' + aFileName +'"') + CRLF);
    aPayload.Add(Utf8Encode('content-type: application/octet-string') + CRLF + CRLF);
    aPayload.Add(Read_file(aFileName));
    aPayload.Add(CRLF);
  end;

  procedure AddField(aPayload: TRtcHugeString; aBound: RtcString; aFieldName, aFieldContent: string);
  begin
    aPayload.Add('--' + aBound + CRLF);
    aPayload.Add(Utf8Encode('content-disposition: form-data; name="' + aFieldName + '"') + CRLF + CRLF);
    aPayload.Add(Utf8Encode(aFieldContent));
    aPayload.Add(CRLF);
  end;

var
  I: Integer;
  lPostMultipartRequest: TRtcDataRequest;
  lPostMultipartHttpClient: TRtcHttpClient;
  lPayload: TRtcHugeString;
  lBound: RtcString;
begin
  lPayload := TRtcHugeString.Create;
  lPostMultipartRequest := TRtcDataRequest.Create(nil);
  lPostMultipartHttpClient := TRtcHttpClient.Create(nil);
  try
    with lPostMultipartHttpClient do
    begin
      AutoConnect := true;
      Blocking := true;
      ReconnectOn.ConnectLost := true;
      ServerAddr := 'www.aserver.com'; //GUploadUtilsServerAddress;
      ServerPort := '80'; //GUploadUtilsServerPort;
      UseSSL := false; //GUploadUtilsUseSSL;
    end;

    lBound := IntToHex(Random(MaxInt), 8) + '_MySystem_boundary';

    with lPostMultipartRequest do
    begin
      Client := lPostMultipartHttpClient;
      OnBeginRequest := TClientUtilsEventClass.PostMultipartBeginRequest;
      OnDataSent := TClientUtilsEventClass.PostMultipartDataSent;
      OnDataReceived := TClientUtilsEventClass.PostMultipartDataReceived;
      Request.Method := 'POST';

      Request.FileName := '/' + aCommand;
      Request.Host := lPostMultipartHttpClient.ServerAddr + ':' + lPostMultipartHttpClient.ServerPort;

      // Request.Value[''] / Request.Cookies / et. al.

        //  Payload/Content Body call once for each field
      AddField(lPayload, lBound, 'form_field_name', 'Form field value');

//  Attachments...
      for I := 0 to lAttachments.Count - 1 do
      begin
        AddFile(lPayload, lBound, lAttachments[I]);
      end;

      lPayload.Add('--' + lBound + '--' + CRLF);

      Request.Info.asByteArray['payload'] := lPayload.GetEx;
      Request.ContentLength := lPayload.Size;
      Request.ContentType := 'multipart/form-data; boundary=' + lBound;

      Post;
    end;

    with lPostMultipartHttpClient do
    begin
      DisconnectNow;
      if not Info.isNull['response'] then
        Result := Info['response'];
      end;
    end;
  finally
    lPostMultipartHttpClient.Free;
    lPostMultipartRequest.Free;
    lPayload.Free;
  end;
end;

TIA,

/Dany
Logged
D.Tkalcec (RTC)
Administrator
*****
Posts: 1881


« Reply #1 on: November 08, 2014, 12:42:08 PM »

I haven't analyzed every single line of your code in detail, but here are some optional improvement suggestions in regards to RTC component and class usage:

1. Instead of using Cli.Write(RtcBytesToString(...)), which is converting a byte array to a string just to be converted back to a byte array inside the Write method (because RTC SDK is using RtcByteArray internally since v6.0), I would recommend using the Cli.WriteEx method when your source is a byte array. If a result of the Copy() function is not accepted as a parameter to the WriteEx method, an explicit typecast to RtcBytesArray should do the trick: Cli.WriteEx(RtcByteArray(...));

2. Do not use the Variant type, unless you absolutely have to. Conversions to and from Variant can be expensive, CPU and Memory-wise. So ... instead of using Cli.Info['response']:=Cli.Read, I would recommend using Cli.Info.asString['response']:=Cli.Read if you want your PostHTTPMultiPart function to return a RtcString, or Cli.Info.asByteArray['response']:=Cli.ReadEx if you want your PostHTTPMultiPart function to return a RtcByteArray. This is not absolutely necessary, but it avoids implicit type conversions.

3. After placing the request into the request queue using the Post method, use the WaitForCompletion method if you want to wait for a response from the Server before continuing. This will ensure that your implementation works on all platforms in single-threaded and multi-threaded mode, independent of the underlying API. Otherwise, you are forced to use a blocking API in single-threaded mode (as you did in your example).

Best Regards,
Danijel Tkalcec
Logged
Dany
RTC License++
*****
Posts: 69


« Reply #2 on: November 09, 2014, 01:51:57 PM »

Brilliant! Your input is much appreciated.

/Dany
Logged
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.21 | SMF © 2015, Simple Machines Valid XHTML 1.0! Valid CSS!
Page created in 0.024 seconds with 17 queries.