Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make request URI / UriInfo available in MessageBodyReader for client (and not just server) #796

Open
kaefer3000 opened this issue Sep 16, 2019 · 6 comments
Assignees
Labels
enhancement New feature or request spec

Comments

@kaefer3000
Copy link

tl;dr: UriInfo is not available in a MessageBodyReader of a client implementation. Thus, I cannot get the request's URI to resolve relative URIs against.

Long description: I am implementing a custom MessageBodyReader (for processing RDF) that needs to resolve URIs against the request URI during processing. If the MessageBodyReader is used in a JAX-RS server implementation, which eg. processes incoming POST requests, I can obtain the request URI from a UriInfo object that is injected at runtime using @Context (as described eg. at https://stackoverflow.com/a/3314076):

public class MyProvider implements MessageBodyReader {
      
  @javax.ws.rs.core.Context
  javax.ws.rs.core.UriInfo uriInfo;

  @Override
  public Iterable<URI> readFrom(Class<Iterable<URI>> clazz, Type genericType,
      Annotation annotations[], MediaType mediaType,
      MultivaluedMap httpHeaders, InputStream entityStream)
        throws IOException, WebApplicationException {

    URI requestURI = uriinfo.getAbsolutePath(); // NPE if called in a client

    // Parsing entityStream resolving relative URIs using requestURI

  }
}

If the MessageBodyReader is called in a JAX-RS client implementation during the reading of an Response's entity, the injection does not happen, hence I get a NullPointerException (see above for the line) when I write a client using JAX-RS like:

Iterable<URI> it = ClientBuilder.newClient().target("http://example.org/").request().get()
    .readEntity(new GenericType<Iterable<URI>>(){});

My current workaround: Having read Chapter 10.4 "Filter and interceptor execution order" of the Jersey documentation, I found that in step 16, the ClientResponseFilter, I have access to both the request URI and the response headers. Hence I wrote a ClientResponseFilter that puts the request URI in a custom response header. Then, I can retrieve that custom header from the 5th parameter of readFrom(...).

Full disclosure: A while ago, I posted this issue as a question on StackOverflow, but did not get much of a response apart from appreciation for my workaround. As I think there may be something fundamental behind it and I think server and client implementations should work analogously, I am filing my problem as an issue here now.

@kalgon
Copy link

kalgon commented Oct 17, 2019

I would really like MessageBodyReader and MessageBodyWriter to be redesigned like this:

public interface MessageBodyContext<T> {
    Class<T> getType();
    Type getGenericType();
    Annotation[] getAnnotations();
    MediaType getMediaType();
    MultivaluedMap<String, String> getHttpHeaders();
    Object getProperty(String name);
    Collection<String> getPropertyNames();
    void setProperty(String name, Object object);
    void removeProperty(String name);
}

public interface MessageBodyReader<T> {
    boolean isReadable(MessageBodyContext<?> context);
    T readFrom(MessageBodyContext<T> context, InputStream entityStream) throws IOException, WebApplicationException;
}

public interface MessageBodyWriter<T> {
    boolean isWriteable(MessageBodyContext<?> context);
    void writeTo(T t, MessageBodyContext<T> context, OutputStream entityStream) throws IOException, WebApplicationException;
    default long getSize(T t, MessageBodyContext<T> context) {
        return -1L;
    }
}

This would reduce the number of parameters in the methods and would give a solution to the original problem.

You could implement some ClientRequestFilter to place the needed info in the properties:

public class MyClientRequestFilter implements ClientRequestFilter {
    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.setProperty("uri", requestContext.getUri());
    }
}

and then retrieve it in your MessageBodyReader:

public class MyMessageBodyReader implements MessageBodyReader<MyObject> {
    @Override
    public MyObject readFrom(MessageBodyContext<MyObject> context, InputStream entityStream) throws IOException, WebApplicationException {
        URI uri = (URI) context.getProperty("uri");
        // do something with the URI
    }
}

@chkal
Copy link
Contributor

chkal commented Oct 19, 2019

While I agree that using something like MessageBodyContext would have been a good idea for the MBR/MBW API, I doubt that it will be possible to refactor the interfaces this way, as it breaks backwards compatibility.

@mkarg
Copy link
Contributor

mkarg commented Oct 19, 2019

The question is, why a server runtime injects UriInfo while a client runtime does not? As the invocation of the MBR on the client is a reaction upon a request triggered by the same client runtime, the client runtime instance simply could put the URI in its request context to fulfil this demand. Mayb we could add that to the spec in 2.2. or 3.0? That wouldn't break backwards compatibility as it works as before but adds a possibility ontop.

@kalgon
Copy link

kalgon commented Oct 19, 2019

Enlarging the number of injectable items would be nice. Do you think it would also be possible to inject Properties and Client into the MBR/MBW? When my MBR/MBW need some additional information from the request which can't be stringified and put into the headers, I work around the limitation by adding some fake Annotation into the passed annotations array (and I am not very proud of it).

@mkarg mkarg self-assigned this Oct 19, 2019
@mkarg mkarg added enhancement New feature or request spec labels Oct 19, 2019
@kalgon
Copy link

kalgon commented Nov 7, 2019

@chkal If Jakarta EE specs are going to change their namespace/package, that will break backwards compatibility anyway. So, isn't it the perfect (and only) opportunity to fix some methods signatures and other things that couldn't have been fixed otherwise without breaking backwards compatibility?

@mkarg
Copy link
Contributor

mkarg commented Nov 7, 2019

@chkal If Jakarta EE specs are going to change their namespace/package, that will break backwards compatibility anyway. So, isn't it the perfect (and only) opportunity to fix some methods signatures and other things that couldn't have been fixed otherwise without breaking backwards compatibility?

There is consensus among the committers to JAX-RS and among the Jakarta EE contributors at-large that the next release (Jakarta EE 9 / JAX-RS 2.2) should be backwards-compatible as best as possible (besides the package rename), and that intentionally breaking backwards compatibility to be deferred for a later release (e. g. Jakarta EE 10 / JAX-RS 3.x).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request spec
Projects
None yet
Development

No branches or pull requests

4 participants