Search code examples
delphiindy

Indy Http Server delivers Javascript with Syntax errors


I am trying to use Indy to serve Javascript (deploying a Swagger UI to render API documentation).

procedure TfmMain.SendJavaScriptFileResponse(AResponseInfo: TIdHTTPResponseInfo; AFileName: String);
begin
  AResponseInfo.ContentType := 'application/javascript';
  AResponseInfo.CharSet := 'utf-8';
  var LFileContents := TStringList.Create;
  try
    LFileContents.LoadFromFile(AFileName);
    AResponseInfo.ContentText := LFileContents.Text;
  finally
    LFileContents.Free;
  end;
end;

When the browser receives the Javascript and attempts to run it, I get a syntax error:

Uncaught SyntaxError: illegal character U+20AC

The respoinsde headers received from the Indy IdHttpServer look like so:

HTTP/1.1 200 OK
Connection: close
Content-Encoding: utf-8
Content-Type: application/javascript; charset=utf-8
Content-Length: 1063786
Date: Sun, 05 Feb 2023 20:45:56 GMT

However, when I serve the exact same Javascript files via my hosted website, the Javascript runs fine in the browser with no errors.

Is there a setting or character set I need to use when sending Javascript files using the Indy HTTP server?


Solution

  • You are loading the Javascript from a file into a string, and then you are sending that string to the client. That requires 2 data conversions at runtime - from the file's encoding to UTF-16 in memory, and from UTF-16 to the specified AResponseInfo.Charset on the data transmission to the client. Either one of those conversions can fail if you are not careful.

    In memory, a string in Delphi 2009+ is always UTF-16 encoded, but you are not specifying the file's encoding when loading the file into the TStringList. So, if the file uses an encoding other than ASCII (say, UTF-8), does not have a BOM, and contains any non-ASCII characters (say, the Euro sign ), then TStringList WILL NOT decode the file into UTF-16 correctly. In which case, you MUST specify the file's actual encoding, eg:

    procedure TfmMain.SendJavaScriptFileResponse(
      AResponseInfo: TIdHTTPResponseInfo;
      const AFileName: String);
    begin
      AResponseInfo.ContentType := 'application/javascript';
      AResponseInfo.CharSet := 'utf-8';
      var LFileContents := TStringList.Create;
      try
        LFileContents.LoadFromFile(AFileName, TEncoding.UTF8); // <-- HERE
        AResponseInfo.ContentText := LFileContents.Text;
      finally
        LFileContents.Free;
      end;
    end;
    

    Another option is to send the actual file itself, without having to load and decode it into memory first, eg:

    procedure TfmMain.SendJavaScriptFileResponse(
      AContext: TIdContext;
      AResponseInfo: TIdHTTPResponseInfo;
      const AFileName: String);
    begin
      AResponseInfo.ContentType := 'application/javascript';
      AResponseInfo.CharSet := 'utf-8';
      AResponseInfo.ServeFile(AContext, AFileName);
    end;
    

    Either way, utf-8 is not a valid value for the HTTP Content-Encoding header. Indy does not assign any value to that header by default, so you must be assigning it manually. Don't do that in this case.