You are not logged in.
Pages: 1
Hi, I had this problem and made the post using IA. I fixed it overwritting the headers in the service caller but I don't know if it is a bug or not.
Thanks in advance,
# Possible mORMot2 bug: TServiceCustomAnswer.Header contains full HTTP response headers when proxying SOA services
**Environment**
- mORMot2 2.4
- Delphi 12
- HTTP server using `TRestHttpServer`
- SOA interfaces using `TServiceCustomAnswer`
- SOA client using `TRestHttpClientWinHttp`
## Problem description
I have two mORMot2 REST servers:
1. **Magnus Server**, which exposes an SOA interface returning `TServiceCustomAnswer`, used to retrieve PDF documents.
2. **B2B Server**, which acts as a proxy, consuming the Magnus SOA interface through `TRestHttpClientWinHttp` and returning the same `TServiceCustomAnswer` to its clients.
Everything works correctly when the remote service returns HTTP 200 with a PDF.
However, when the remote service returns HTTP 404 with a JSON error, the proxy server unexpectedly returns:
```json
{
"errorCode": 500,
"errorText": "Unsafe HTTP header rejected"
}
```
## Original service implementation
The Magnus service returns a PDF or raises an exception if the file does not exist:
```pascal
if not FileExists(FName) then raise EExNotFound.Create('FA No encontrada');
Result.Content := StringFromFile(FName);
Result.Header := 'Content-Type: application/pdf'#13#10 + 'Content-Disposition: inline; filename="' + StringToUtf8(ExtractFileName(FName)) + '"';
Result.Status := 200;
```
The exception handler correctly converts `EExNotFound` into HTTP 404 with this JSON response:
```json
{
"errorCode": 404,
"error": {
"code": "NOT_FOUND",
"message": "FA No encontrada"
}
}
```
The remote Magnus server behaves correctly.
## Problem in the proxy
The B2B server invokes the remote SOA method using `TRestHttpClientWinHttp`.
The client receives the expected response:
```text
POST MagnusServiceLocalB2B.FactPdf status=404
```
But when B2B attempts to return the resulting `TServiceCustomAnswer`, it fails with HTTP 500.
Using the debugger, I inspected the B2B server's `Call` context:
**Call.OutStatus:**
```text
404
```
**Call.OutBody:**
```json
{
"errorCode": 404,
"error": {
"code": "NOT_FOUND",
"message": "FA No encontrada"
}
}
```
**Call.OutHead:**
```text
HTTP/1.1 404 Not Found\r\n
Content-Length: 83\r\n
Content-Type: application/json\r\n
Server: mORMot2 (Win)\r\n
X-Powered-By: mORMot 2.4\r\n
\r\n
```
The issue is that `Call.OutHead` contains the **complete HTTP response header block**, including the HTTP status line and the final empty line.
This causes `IsInvalidHttpHeader(Call.OutHead)` to reject the headers.
## Relevant mORMot2 source code
In `mormot.soa.client.pas`, inside `TServiceFactoryClient.InternalInvoke`, the client copies the received headers into the custom answer:
```pascal
aServiceCustomAnswer^.Status := status;
aServiceCustomAnswer^.Header := head;
aServiceCustomAnswer^.Content := resp;
```
When the SOA server subsequently processes this `TServiceCustomAnswer`, the header is assigned to the outgoing HTTP headers.
In `mormot.rest.server.pas`, the following validation fails:
```pascal
if (Call.OutHead <> '') and
not (rsoHttpHeaderCheckDisable in fOptions) and
IsInvalidHttpHeader(Call.OutHead) then
ctxt.Error('Unsafe HTTP header rejected', HTTP_SERVERERROR);
```
This suggests a mismatch between the HTTP headers returned by the SOA client and the format expected by the SOA server when forwarding `TServiceCustomAnswer`.
## Working workaround
I resolved the issue by explicitly replacing the headers in the B2B proxy after the remote call:
```pascal
if Result.Status = 200 then
Result.Header := 'Content-Type: application/pdf'#13#10 + 'Content-Disposition: inline; filename="' + IdDoc.ToString + '.PDF"'
else
Result.Header := 'Content-Type: application/json';
```
After this modification:
- HTTP 200 responses continue to return the PDF correctly.
- HTTP 404 responses preserve the original status code.
- The original JSON error body is preserved.
- The proxy no longer returns HTTP 500.
- No modification to the original Magnus service or its exception handler is necessary.
## Question for the mORMot2 developers
Is this the intended behavior of `TServiceCustomAnswer.Header` when returned by `TRestHttpClientWinHttp`?
Should `TServiceFactoryClient.InternalInvoke` normalize the received HTTP headers before assigning them to `aServiceCustomAnswer^.Header`, removing the HTTP status line and the terminating empty line?
Alternatively, should the SOA server normalize these headers before assigning them to `Call.OutHead`?
It seems that directly forwarding a `TServiceCustomAnswer` between two mORMot2 SOA services can produce an invalid HTTP response, particularly when the upstream service returns a non-200 status.
Would it make sense to address this normalization in mORMot2 itself?
Thank you.
Pages: 1