Couple of quick HTTPApi questions

Couple of quick questions. Thought it was clearer to start a separate topic in the end.

Firstly, are enumerated types supported as parameters to REST functions? I tried it but the Swagger editor complains about the resulting API.

Secondly, and more importantly right now, are path parameters allowed in PUT and DELETE methods?
Whilst I have a GET method which uses a path parameter and works fine, I have some PUT and DELETE methods which also use a path parameter but, when I try to call these from Postman, I get an ā€œInvalid Requestā€ error and I have no idea why.

Further to this, basically if I have this:

[ROServiceMethod]
[ROCustom('HttpApiPath','admin/user/{username}')]
[ROCustom('HttpApiMethod','DELETE')]
procedure DeleteUser(username : string);

Then trying to call from Postman gives me an invalid request error.

Literally changing the HttpApiMethod to ā€˜GETā€™ and it works fine.

Any ideas why this could be?

this code:

  MyEnum = (enum1, enum2, enum3);
..
   [ROServiceMethod]
   [ROCustom('HttpApiPath','test')]
   [ROCustom('HttpApiMethod','PUT')]
   function Dotest(searchname : string) : MyEnum;
  end;

generates

{"swagger":"2.0","info":{"title":"NewProjectLibrary","version":"1.0.0"},"host":"localhost:8099","basePath":"\/api","schemes":["http"],"consumes":["application\/json"],"produces":["application\/json"],"paths":{"\/test":{"put":{"parameters":[{"name":"NewProjectServiceDotestRequest","in":"body","schema":{"$ref":"#\/definitions\/NewProjectServiceDotestRequest"}}],"responses":{"200":{"description":"The method call completed successfully","schema":{"$ref":"#\/definitions\/MyEnum"}},"400":{"description":"Bad Request"},"500":{"description":"Internal Server Error"}}}}},"definitions":{"NewProjectServiceDotestRequest":{"type":"object","properties":{"searchname":{"type":"string"}}},"MyEnum":{"type":"string","enum":["enum1","enum2","enum3"]}}}

and editor has no issue with it:


Iā€™ve tested it:

    [ROServiceMethod]
    [ROCustom('HttpApiPath','admin/user/{username}')]
    [ROCustom('HttpApiMethod','DELETE')]
    function DeleteUser(username : string): string;
    [ROServiceMethod]
    [ROCustom('HttpApiPath','admin/user/{username}')]
    [ROCustom('HttpApiMethod','PUT')]
    function DeleteUser2(username : string): string;
...

function TNewProjectService.DeleteUser(username: string): string;
begin
  Result := 'DELETE:  '+username;
end;

function TNewProjectService.DeleteUser2(username: string): string;
begin
  Result := 'PUT:  '+username;
end;
C:\>curl -v -X PUT "http://localhost:8099/api/admin/user/aaa" -H "accept: application/json"
*   Trying ::1:8099...
*   Trying 127.0.0.1:8099...
* Connected to localhost (127.0.0.1) port 8099 (#0)
> PUT /api/admin/user/aaa HTTP/1.1
> Host: localhost:8099
> User-Agent: curl/7.72.0
> accept: application/json
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: close
< Content-Type: application/json; charset=utf-8
< Content-Length: 11
< Date: Thu, 18 Aug 2022 13:15:23 GMT
< Accept-Encoding: gzip, identity
<
"PUT:  aaa"* Closing connection 0
C:\>curl -v -X DELETE "http://localhost:8099/api/admin/user/aaa" -H "accept: application/json"
*   Trying ::1:8099...
*   Trying 127.0.0.1:8099...
* Connected to localhost (127.0.0.1) port 8099 (#0)
> DELETE /api/admin/user/aaa HTTP/1.1
> Host: localhost:8099
> User-Agent: curl/7.72.0
> accept: application/json
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: close
< Content-Type: application/json; charset=utf-8
< Content-Length: 14
< Date: Thu, 18 Aug 2022 13:15:26 GMT
< Accept-Encoding: gzip, identity
<
"DELETE:  aaa"
* Closing connection 0

Ok thanks. Will do some more testing on the DELETE, maybe Postman is doing something weird.

As for the enumerated types - your example is using it as the return value whereas my issue was when using it as a parameter type.

Hi,

it doesnā€™t matter:

  MyEnum = (enum1, enum2, enum3);
...
   [ROServiceMethod]
   [ROCustom('HttpApiPath','test')]
   [ROCustom('HttpApiMethod','PUT')]
   function Dotest(searchname : MyEnum) : string;
...
function TNewProjectService.Dotest(searchname: MyEnum): string;
begin
result := 'ok';
end;
{"swagger":"2.0","info":{"title":"NewProjectLibrary","version":"1.0.0"},"host":"localhost:8099","basePath":"\/api","schemes":["http"],"consumes":["application\/json"],"produces":["application\/json"],"paths":{"\/test":{"put":{"parameters":[{"name":"NewProjectServiceDotestRequest","in":"body","schema":{"$ref":"#\/definitions\/NewProjectServiceDotestRequest"}}],"responses":{"200":{"description":"The method call completed successfully","schema":{"type":"string"}},"400":{"description":"Bad Request"},"500":{"description":"Internal Server Error"}}}}},"definitions":{"NewProjectServiceDotestRequest":{"type":"object","properties":{"searchname":{"$ref":"#\/definitions\/MyEnum"}}},"MyEnum":{"type":"string","enum":["enum1","enum2","enum3"]}}}

C:\>curl -v -X PUT "http://localhost:8099/api/test" -H "accept: application/json" -H "content-type: application/json" -d "{ \"searchname\": \"enum1\"}"
*   Trying ::1:8099...
*   Trying 127.0.0.1:8099...
* Connected to localhost (127.0.0.1) port 8099 (#0)
> PUT /api/test HTTP/1.1
> Host: localhost:8099
> User-Agent: curl/7.72.0
> accept: application/json
> content-type: application/json
> Content-Length: 24
>
* upload completely sent off: 24 out of 24 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Connection: close
< Content-Type: application/json; charset=utf-8
< Content-Length: 4
< Date: Thu, 18 Aug 2022 13:29:50 GMT
< Accept-Encoding: gzip, identity
<
"ok"* Closing connection 0

Ok Iā€™ll try the enums again - the swagger editor was throwing some validation issues when I tried it.

As for the DELETE, Iā€™m getting this:

curl -v -X DELETE "http://localhost:39001/api/admin/user/aaa" -H "accept: application/json"
*   Trying 127.0.0.1:39001...
* Connected to localhost (127.0.0.1) port 39001 (#0)
> DELETE /api/admin/user/aaa HTTP/1.1
> Host: localhost:39001
> User-Agent: curl/7.83.1
> accept: application/json
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< Content-Type: text/html; charset=utf-8
< Connection: close
< Content-Length: 93
<
* Excess found in a read: excess = 840, size = 93, maxdownload = 93, bytecount = 0
<html><head><title>Invalid Request</title></head><body><h1>Invalid Request</h1></body></html>* Closing connection 0

The key differences here are obviously the 500 return code but also that the Content-Type is showing as text/html rather than the application/json you were getting. Not sure if this is relevant.

Iā€™m clearly doing something different/wrong but I canā€™t see exactly what.

Hi,

I can review what is wrong, but I need your simple testcase.
you can drop it to support@ for keeping privacy

Ok, Iā€™ve created a stripped down test application which still reproduces the above issue, at least for me.

Thereā€™s nothing important in this stripped down version so will attach it here.

Iā€™ve also included a second test method with the enumerated type parameter. Pasting the generated API into the Swagger editor gives a validation error for me. I canā€™t actually test the function as itā€™s suffering from the same issue as the other one and giving a 500 error.

TestServer.rar (27.7 KB)

Hi,

I can reproduce validation errors.
Looks like some rules werenā€™t mentioned at OpenAPI Specification - Version 2.0 | Swagger

for example, query parameters could be applied only to GET method, etc ā€¦

Ah ok, do you mean you get the Swagger editor validation error on the enumerated type parameter?
The DELETE method wasnā€™t giving any validation error for me in the Swagger editor, it just didnā€™t work when I called it.

I think there are two separate issues here. Firstly the enumerated type parameter throws a validation error in the Swagger editor and, whilst the API imports into Postman, the enumerated type parameter is missing. Not sure why this is, as you said this is permitted, but itā€™s not critical to me right now.

The second, more important problem is why the DELETE method fails with a single query parameter.
This gives a 500 error for me and indeed debugging shows it never gets as far as calling the actual function.

Iā€™m really confused now as your comment suggests that the spec says query parameters are only permitted for GET methods yet it works fine for you and my searching has shown plenty of people using DELETE methods with query parameters.

Logged as bugs://D19279.

Hi,

good catch.
TROAsyncHttpServer supports only OPTIONS, PUT and GET requests.

pls update uROAsyncHttpServer.pas as

procedure TROAsyncContext.cbFirstLine(aSender: TROAsyncSocket);
..
  if (fRequest.Method <> id_Method_Get) and
     (fRequest.Method <> id_Method_POST) and
     (fRequest.Method <> id_Method_Options) and
     (fRequest.Method <> id_Method_Put) and
     (fRequest.Method <> id_Method_Merge) and
     (fRequest.Method <> id_Method_Delete) and
     (fRequest.Method <> id_Method_Head) then begin  // `if` was changed
    SendInvalidRequest;  
    Exit;
  end;

Ok cool will give that a try.

Weird how it seemed to work for you though.

Iā€™ve used IndyHTTPServer.
this is default one from template

bugs://D19279 was closed as fixed.

Ahhh gotcha!

There actually isnā€™t any specific reason Iā€™m using that particular server channel. Which is ā€˜bestā€™ for a REST server or which would you generally recommend? I never know which to choose tbh.

Hi,

it depends on your environment. In some cases - indy is better, in other - WinHTTP, in third - our native server.
We give possibility to choose server to user.

Yeah I appreciate the choice, I just never know what criteria to use to select one of the servers.

I think I went with the RO one mainly as I found it the easiest one to get SSL working with.

1 Like

The above code fix fails to compile as it doesnā€™t recognise the id_Method_xxx definitions and I canā€™t see these defined anywhere.

Iā€™m on 1521 - have these possibly been added in a later version?

from uROHTTPTools.pas

  id_Method_Options = 'OPTIONS';
  id_Method_Get     = 'GET';
  id_Method_Post    = 'POST';
  id_Method_Put     = 'PUT';
  id_Method_Merge   = 'MERGE';
  id_Method_Delete  = 'DELETE';
  id_Method_Head    = 'HEAD';

sure