RejectedSoftware Forums

Sign up

Some frictions I encountered with vibe.web.rest

In general I appreciate the method of constructing an Interface and a derived Class for modeling rest endpoints. But there were some frictions I had, including:

1) Unable to set/read headers manually since HTTPServer(Request|Response) is not available. E.g. for CORS or manual cache-headers, reading custom headers, branch on Accept header etc.

2) I want to run some code before some endpoints. But @before requires your function to emit a value that gets inserted into the endpoint's function. Current workaround is registering a catch-all route - router.any("*", &doYourStuff);. But that is obviously too much.

3) I have a rest endpoint that needs to return a generated XML file.

4) Proper CORS pre-flight handling.

This makes it impossible for me to use vibe.web.rest.

Is there awareness about these issues? Do I need to post bugs?

I would gladly help with implementing some features.

Re: Some frictions I encountered with vibe.web.rest

Am 29.09.2015 um 07:26 schrieb Sebastiaan Koppe:

In general I appreciate the method of constructing an Interface and a derived Class for modeling rest endpoints. But there were some frictions I had, including:

1) Unable to set/read headers manually since HTTPServer(Request|Response) is not available. E.g. for CORS or manual cache-headers, reading custom headers, branch on Accept header etc.

Have you seen the @headerParam annotation? It lets you map a single
parameter to a single header field. As of 0.7.25 it also supports ref
and out parameters to write to the response object.

2) I want to run some code before some endpoints. But @before requires your function to emit a value that gets inserted into the endpoint's function. Current workaround is registering a catch-all route - router.any("*", &doYourStuff);. But that is obviously too much.

Do you have an example for this? I'm asking because ideally a solution
for this would be agnostic to the underlying HTTP transport. One of my
goals for this system is that the implementation class is also usable
using direct D calls (i.e. not only by using the RestInterfaceClient!T
as a proxy).

3) I have a rest endpoint that needs to return a generated XML file.

We just need to decide on a design for this, as the original
implementation was targeted at a pure JSON based protocol. Some possible
(non-exclusive) approaches:

  • Support OutputStream parameters and @contentType annotations,
    similar to the web interface generator. This is mainly useful for
    returning large binary data blobs (files), but would lose static typing
    in the D interface.
  • Recognize XMLDocument as a return type. This would solve cases
    where only a few routes would return XML.
  • Implement general support for XML as an alternative to JSON (decide
    based on "accepts" header.
  • Make formats other than JSON pluggable (i.e. pass custom
    serialization routines that work on paramters and return values in some
    way).

4) Proper CORS pre-flight handling.

This makes it impossible for me to use vibe.web.rest.

This is indeed bad to be missing in some situations (of course it can be
worked around manually, but that's a bit besides the point of the
interface generator). There is an open issue
(#546) for this.

If you'd like to lend a hand, this would probably be the best candidate.
It should be relatively straight forward to implement with the recent
RestInterface!T refactoring in place.

Is there awareness about these issues? Do I need to post bugs?

I would gladly help with implementing some features.

Re: Some frictions I encountered with vibe.web.rest

On Wed, 30 Sep 2015 09:08:00 +0200, Sönke Ludwig wrote:

Have you seen the @headerParam annotation? It lets you map a single
parameter to a single header field. As of 0.7.25 it also supports ref
and out parameters to write to the response object.

That is nice.

2) I want to run some code before some endpoints.

Do you have an example for this? I'm asking because ideally a solution
for this would be agnostic to the underlying HTTP transport.

The std.range equivalent would be tee. A concrete example would be a logging function, (or just middleware). I my case I was trying to make CORS work using @before.

Generally it is about @before functions which don't create extra variables.

3) I have a rest endpoint that needs to return a generated XML file.

We just need to decide on a design for this, as the original
implementation was targeted at a pure JSON based protocol. Some possible
(non-exclusive) approaches:

  • Support OutputStream parameters and @contentType annotations,

similar to the web interface generator. This is mainly useful for
returning large binary data blobs (files), but would lose static typing
in the D interface.

Seems like a good approach.

I have had API's though which gave different content types based on the accept header. So if the request was made with application/json as accept-type, it would return json. If application/xml, it would return xml. Or JSONP.

  • Recognize XMLDocument as a return type. This would solve cases

where only a few routes would return XML.

Nah. What if I want to generate captcha's? Or I want to return xlsx?

  • Implement general support for XML as an alternative to JSON (decide

based on "accepts" header.

That gets tricky since there is no 1-to-1 relationship. (plus you would have to deal with namespaces and all).

  • Make formats other than JSON pluggable (i.e. pass custom

serialization routines that work on paramters and return values in some
way).

I like the OutputStream the most. Seems easiest to implement and since it is simply a ubyte[] sink, people can put whatever they want.

4) Proper CORS pre-flight handling.

This makes it impossible for me to use vibe.web.rest.

If you'd like to lend a hand, this would probably be the best candidate.
It should be relatively straight forward to implement with the recent
RestInterface!T refactoring in place.

Yeah, that seems quite doable.

On a sidenote, I always wondered why CORS has the Access-Control-Request-Headers header. As if a server needs to determine access control based on the (custom) headers that were send.

One other problem I had was with exceptions. For normal routes you can supply the error handler in the server settings, which can format a nice response. You see, I always require my services to respond in a machine readable way, even if it was a 500 error. So if something went wrong I want it to return something - besides the http status code - like this:

{
error: 13,
message: "some reason why it failed, mostly aimed for the developer debugging it like: `email in use`",
@debug trace: "foo()@23\nbar@74\n...."
}

This is quite doable with the error handler, but vibe.web.rest I cannot get the exception to be formatted the way I like. Very annoying that it sidesteps the error handler.

Re: Some frictions I encountered with vibe.web.rest

3) I have a rest endpoint that needs to return a generated XML file.

We just need to decide on a design for this, as the original
implementation was targeted at a pure JSON based protocol. Some possible
(non-exclusive) approaches:

  • Support OutputStream parameters and @contentType annotations,
    similar to the web interface generator. This is mainly useful for
    returning large binary data blobs (files), but would lose static typing
    in the D interface.

Seems like a good approach.

I'm also looking for a similar mechanism and already noted it on Github, but since I'm not sure if it gets attention in a PR discussion, I'll also propose it here:

I think it would be a good idea to allow the specification of custom serialization/deserialization code using UDAs. That way, rest interfaces could keep their static typing while giving the programmer control over the representation of his types where needed.

My simplistic example on Github looks like this, but could certainly also be written in terms of streams:

class MyCustomSerializer
{
    ubyte[] serialize(T value) { /* serialize the value */ }
    T deserialize(ubyte[] serialized) { /* de-serialize the data */ }
}

interface IMyRestInterface
{
    @serialized!(new MyCustomSerializer())
    T getSomething();
}