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
Add typed body on request and response #124
Conversation
build.sbt
Outdated
// JSON Readables and Writables | ||
//--------------------------------------------------------------- | ||
|
||
lazy val `play-ahc-ws-standalone-json` = project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't we just have play-ws-standalone-json
and not have a dependency on ahc? I suppose it doesn't matter much since AHC is the only real implementation we have, but just wondering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well the problem here is probably that we need the underlying response/request type, which is a ahc class.
this.contentType = contentType; | ||
} | ||
|
||
class InMemoryBody extends AbstractWSBody<ByteString> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this just a special case of the streamed body?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well I think for performance reasons it's good to have both.
* | ||
* @param <A> the type of body. | ||
*/ | ||
public interface BodyWritable<A> extends Supplier<WSBody<A>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we could use a common interface for both the Writeable
interface used for Play bodies and this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not saying that needs to be implemented now but would be interested in getting your thoughts on if that'd be eventually possible to write a single implementation that works for Play action and WS bodies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tricky as we really need something that does => Source[ByteString]
as the base type, and Writable
does ByteString
.
README.md
Outdated
} | ||
``` | ||
|
||
Note that there is a special case: when you are using a stream, then you can get the body as a Source: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if I try to do this on a non-streamed body?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source.single(response.bodyAsBytes)
:-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well small changes needed, but besides them it looks really really good.
Hm. The only thing which gmethvin already mentioned is that the play-json/xml readable/writeables need to have a direct dependency on ahc. But I think there is not a lot to go around that.
*/ | ||
public interface DefaultBodyWritables { | ||
|
||
DefaultBodyWritables instance = new DefaultBodyWritables() {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for what is this instance
? Especially since it's package-private?
} | ||
|
||
// Always replace the content type header to make sure exactly one exists | ||
List<String> contentTypeList = new ArrayList<>(); | ||
contentTypeList.add(contentType); | ||
List<String> contentTypeList = Collections.singletonList(contentType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might just inline it:
possiblyModifiedHeaders.set(HttpHeaders.Names.CONTENT_TYPE, Collections.singletonList(contentType))
.
I mean the possibility is high that the java compiler will do that, but I think there is no reason to keep a variable around just for passing it a line below
README.md
Outdated
To use the default type mappings in Java, you should use the following: | ||
|
||
```java | ||
import static play.libs.ws.ahc.DefaultBodyReadables.instance.*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is incorrect.
Basically you can't statically import default methods.
Well In the Java API I would prefer to have both ways either to have:
import play.libs.ws.ahc.DefaultBodyReadables;
import play.libs.ws.ahc.DefaultBodyWritables;
public class MyClient implements DefaultBodyWritables, DefaultBodyReadables {
public CompletionStage<String> doStuff() {
return client.url("http://example.com").post(body("hello world")).thenApply(response ->
response.body(string())
);
}
}
and a second version which does not have a implements:
import static play.libs.ws.ahc.DefaultBodyReadables.Static.*;
import static play.libs.ws.ahc.DefaultBodyWritables.Static.*;
public class MyClient implements DefaultBodyWritables, DefaultBodyReadables {
public CompletionStage<String> doStuff() {
return client.url("http://example.com").post(body("hello world")).thenApply(response ->
response.body(string())
);
}
}
Example:
public interface DefaultBodyWritables {
DefaultBodyWritables instance = new DefaultBodyWritables() {};
/**
* Creates a {@link InMemoryBodyWritable} from a String, setting the content-type to "text/plain".
*
* @param s the string to pass in.
* @return a {@link InMemoryBodyWritable} instance.
*/
default InMemoryBodyWritable body(String s) {
return new InMemoryBodyWritable(ByteString.fromString(s), "text/plain");
}
public final class Static {
static InMemoryBodyWritable body(String s) {
return instance.body(s);
}
}
// ...
I think that's the only way to mimic the Scala object DefaultBodyWritables
. Another way would be to declare all methods as static
then nobody would need to implement the interfaces, which is probably more java-ish but also harder for backwards compatibility.
build.sbt
Outdated
// JSON Readables and Writables | ||
//--------------------------------------------------------------- | ||
|
||
lazy val `play-ahc-ws-standalone-json` = project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well the problem here is probably that we need the underlying response/request type, which is a ahc class.
this.contentType = contentType; | ||
} | ||
|
||
class InMemoryBody extends AbstractWSBody<ByteString> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well I think for performance reasons it's good to have both.
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
public interface WSResponseHeaders { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be removed completly since it's no longer in use (or deprecated)
5c4f743
to
e52c314
Compare
} | ||
|
||
@Override | ||
public String getContentType() { | ||
final List<String> values = headers.get(HttpHeaders.Names.CONTENT_BASE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note CONTENT_BASE is the wrong field, so we fix this here
WSRequestExecutor executor = foldRight(r -> { | ||
StandaloneAhcWSRequest ahcWsRequest = (StandaloneAhcWSRequest) r; | ||
Request ahcRequest = ahcWsRequest.buildRequest(); | ||
return client.executeStream(buildRequest(), materializer.executionContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enable request filters for requests that go through stream rather than execute()
stringListMap.forEach((key, values) -> values.forEach(value -> builder.addFormParam(key, value))); | ||
possiblyModifiedHeaders.set(CONTENT_TYPE, singletonList(contentType)); | ||
|
||
if (bodyWritable instanceof InMemoryBodyWritable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only in-memory or source
PR applied changes and now differs from my initial review
e7ddab0
to
0647195
Compare
0647195
to
91c7790
Compare
Add typed body to Play WS. After this, Play-WS should not have any dependencies at all on the body -- the response has a body of
String
/ByteString
/Source[ByteString]
. If you want to get something, you need to add a BodyReadable or BodyWritable function to transform it.So for Scala, you use a context bound:
For Java you do:
And then you can use overloading so it's not quite so ugly:
Although this only works so far, and falls over completely when you use generics, i.e. it can't tell a Source from a Source<Multipart.Part<Source>>, so you have to do things like
For readables, it looks the same, only you have to specify the type. So for Scala:
And for Java, it looks like this: