I'm experimenting with a little web framework and stumbled across an issue with streams. There are handler methods like Response get(HttpServletRequest)
. From the frameworks perspective the response should offer an input stream for the response body. The framework reads this streams and writes the data to the underlying OutputStream of the Servlet API. But from the user's perspective an OutputStream is needed to write to.
Framework's perspective:
public interface Response {
InputStream getInputStream();
}
User's perspective:
public interface Response {
InputStream getOutputStream();
}
It should be used like this:
public void get(HttpServletRequest request) {
Response response = new Response(OK);
response.getOutputStream().write(...);
return response;
}
The problem is that I cannot create an outputstream without an output target (and don't want to write to a ByteArray buffer).
Any idea how to give the user an OutputStream without passing one in 开发者_如何转开发like in the Servlet API? I'd like to avoid output parameters.
Abstract/decorate the Request
as well and get the Response
from it instead.
E.g. in your front controller servlet:
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
Request request = new RequestImpl(req, res);
Action action = ActionFactory.getAction(req); // Do whatever way you do to locate the `Action`.
Response = action.get(request);
// ...
}
wherein RequestImpl
look like this:
public class RequestImpl implements Request {
private HttpServletRequest request;
private HttpServletResponse response;
public RequestImpl(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
public Response newResponse(Status status) {
return new ResponseImpl(response, status);
// Add a boolean responseCreated to avoid creation of multiple responses? Illegal state!
}
public String getParameter(String name) { // Just another example of decorated method.
return request.getParameter(name);
}
// ...
}
and the ResponseImpl
look like this:
public class ResponseImpl implements Response {
private HttpServletResponse response;
public ResponseImpl(HttpServletResponse response, Status status) {
this.response = response;
this.response.setStatus(status.getCode());
}
public OutputStream getOutputStream() {
return response.getOutputStream();
}
// ...
}
which you finally use like this in your Action
:
public ActionImpl implements Action {
public Response get(Request request) {
Response response = request.newResponse(OK);
response.getOutputStream().write("body");
return response;
}
}
Alternatively, you can also create a Context
which takes both the HttpServletRequest
and HttpServletResponse
and pass that in instead of Request
. That's also less or more what the average MVC framework does. E.g.
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
Context context = new ContextImpl(req, res);
Action action = ActionFactory.getAction(req); // Do whatever way you do to locate the `Action`.
action.execute(context);
context.render(); // Do here whatever you'd initially to do with the obtained Response.
}
with
public ActionImpl implements Action {
public void execute(Context context) {
context.getResponseOutputStream().write("body");
}
}
That said, instead of reinventing, I'd suggest to have a look to existing API's as well. Depending on what you'd like to do, JSF, JAX-RS or JAX-WS might be what you're actually after. Unless this is for pure hobby purposes ;)
When you give the client an OutputStream, it HAS to be connected to something, either a buffer, socket, file or something else that can handle the data written to it.
One option might be to set it up as a PipedOutputStream connected to a PipedInputStream in another thread that handled the data. However, that would run into problems if the handling thread couldn't keep up with the client thread.
As Jim Garrison says, an OutputStream
has to be connected to something... the bytes that are written to it have to go somewhere or else be discarded.
It seems to me like what you need to do is have Response
wrap an InputStream
directly. There could be a method on Response
like:
void setContent(InputStream content);
Then, rather than writing to an OutputStream
you provide, the user could provide whatever kind of InputStream
they wanted and your framework would just copy that to the servlet OutputStream
.
You could also allow them provide something implementing some interface similar to this:
interface OutputProducer {
void writeTo(OutputStream out) throws IOException;
}
In both cases you're basically allowing the user to provide a callback to use for writing to the OutputStream
so that you don't need to pass it in.
I don't really know if any of that is worth it just to avoid using an output parameter, but those are options.
精彩评论