Skip to content

Commit

Permalink
#138 Fixed the HTTP response rendering for unmanaged handlers.
Browse files Browse the repository at this point in the history
  • Loading branch information
nmihajlovski committed Aug 23, 2017
1 parent fc17f0b commit ea0e1d4
Show file tree
Hide file tree
Showing 19 changed files with 505 additions and 82 deletions.
21 changes: 2 additions & 19 deletions rapidoid-http-fast/src/main/java/org/rapidoid/http/HttpUtils.java
Expand Up @@ -24,7 +24,6 @@
import java.io.File; import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Future;
import java.util.regex.Pattern; import java.util.regex.Pattern;


/* /*
Expand Down Expand Up @@ -237,6 +236,7 @@ public static byte[] responseToBytes(Req req, Object result, MediaType contentTy
} }


public static void resultToResponse(Req req, Object result) { public static void resultToResponse(Req req, Object result) {

if (result instanceof Req) { if (result instanceof Req) {
if (req != result) { if (req != result) {
req.response().result("Unknown request instance was received as result!"); req.response().result("Unknown request instance was received as result!");
Expand All @@ -247,7 +247,7 @@ public static void resultToResponse(Req req, Object result) {
req.response().result("Unknown response instance was received as result!"); req.response().result("Unknown response instance was received as result!");
} }


} else { } else if (!(result instanceof HttpStatus)) {
req.response().result(result); req.response().result(result);
} }
} }
Expand All @@ -273,23 +273,6 @@ public static BasicConfig zone(Req req) {
return zone(custom, req.zone()); return zone(custom, req.zone());
} }


@SuppressWarnings("unchecked")
public static Object postprocessResult(Req req, Object result) {

if (result instanceof Req || result instanceof Resp || result instanceof HttpStatus) {
return result;

} else if (result == null) {
return null; // not found

} else if ((result instanceof Future<?>) || (result instanceof org.rapidoid.concurrent.Future<?>)) {
return req.async();

} else {
return result;
}
}

public static void setResponseTokenCookie(Resp resp, String token) { public static void setResponseTokenCookie(Resp resp, String token) {
resp.cookie(TOKEN, token, "HttpOnly"); resp.cookie(TOKEN, token, "HttpOnly");
} }
Expand Down
Expand Up @@ -4,10 +4,10 @@
import org.rapidoid.annotation.Since; import org.rapidoid.annotation.Since;
import org.rapidoid.annotation.TransactionMode; import org.rapidoid.annotation.TransactionMode;
import org.rapidoid.ctx.With; import org.rapidoid.ctx.With;
import org.rapidoid.datamodel.Results;
import org.rapidoid.http.*; import org.rapidoid.http.*;
import org.rapidoid.http.customize.Customization; import org.rapidoid.http.customize.Customization;
import org.rapidoid.http.impl.MaybeReq; import org.rapidoid.http.impl.MaybeReq;
import org.rapidoid.http.impl.ReqImpl;
import org.rapidoid.http.impl.RouteOptions; import org.rapidoid.http.impl.RouteOptions;
import org.rapidoid.http.impl.lowlevel.HttpIO; import org.rapidoid.http.impl.lowlevel.HttpIO;
import org.rapidoid.jpa.JPA; import org.rapidoid.jpa.JPA;
Expand All @@ -17,7 +17,6 @@
import org.rapidoid.net.abstracts.Channel; import org.rapidoid.net.abstracts.Channel;
import org.rapidoid.security.Secure; import org.rapidoid.security.Secure;
import org.rapidoid.u.U; import org.rapidoid.u.U;
import org.rapidoid.util.Msc;
import org.rapidoid.util.TokenAuthData; import org.rapidoid.util.TokenAuthData;


import java.util.Collections; import java.util.Collections;
Expand Down Expand Up @@ -78,27 +77,40 @@ private HttpStatus handleNonDecorating(Channel ctx, boolean isKeepAlive, Req req
Object result; Object result;
MaybeReq maybeReq = HttpUtils.maybe(req); MaybeReq maybeReq = HttpUtils.maybe(req);


try { ReqImpl reqq = (ReqImpl) req;
result = handleReqAndPostProcess(ctx, isKeepAlive, req, extra);


result = HttpUtils.postprocessResult(req, result); // handle & post-process
result = handleReqAndPostProcess(ctx, isKeepAlive, req, extra);


if ((req != null && req.isAsync()) || result == HttpStatus.ASYNC) { // the response properties might be overwritten
return HttpStatus.ASYNC; int code = -1;
} MediaType ctype = contentType;
if (req != null && reqq.hasResponseAttached()) {
Resp resp = req.response();
ctype = resp.contentType();
code = resp.code();
}


} catch (Throwable e) { if (result == HttpStatus.NOT_FOUND) {
HttpIO.INSTANCE.writeResponse(maybeReq, ctx, isKeepAlive, 500, contentType, "Internal server error!".getBytes()); http.notFound(ctx, isKeepAlive, ctype, this, req);
Log.error("Error occurred during un-managed request processing", "request", req, "error", e); return HttpStatus.NOT_FOUND;
return HttpStatus.ERROR;
} }


if (contentType == MediaType.JSON) { if (result == HttpStatus.ASYNC) {
HttpIO.INSTANCE.writeAsJson(maybeReq, ctx, 200, isKeepAlive, result); return HttpStatus.ASYNC;
} else { }
HttpIO.INSTANCE.write200(maybeReq, ctx, isKeepAlive, contentType, Msc.toBytes(result));
if (result instanceof Throwable) {
Throwable err = (Throwable) result;
HttpIO.INSTANCE.writeHttpResp(maybeReq, ctx, isKeepAlive, 500, MediaType.PLAIN_TEXT_UTF_8, "Internal server error!".getBytes());

Log.error("Error occurred during unmanaged request processing", "request", req, "error", err);
return HttpStatus.DONE;
} }


int respCode = code > 0 ? code : 200; // default code is 200
HttpIO.INSTANCE.writeHttpResp(maybeReq, ctx, isKeepAlive, respCode, ctype, result);

return HttpStatus.DONE; return HttpStatus.DONE;
} }


Expand Down Expand Up @@ -206,13 +218,11 @@ public void run() {
try { try {


if (!U.isEmpty(wrappers)) { if (!U.isEmpty(wrappers)) {
result = wrap(channel, isKeepAlive, req, 0, extra, wrappers); result = wrap(channel, isKeepAlive, req, 0, extra, wrappers, txMode);
} else { } else {
result = handleReqMaybeInTx(channel, isKeepAlive, req, extra, txMode); result = handleReqMaybeInTx(channel, isKeepAlive, req, extra, txMode);
} }


result = HttpUtils.postprocessResult(req, result);

} catch (Throwable e) { } catch (Throwable e) {
result = e; result = e;
} }
Expand All @@ -222,22 +232,32 @@ public void run() {
}; };
} }


private Object handleReqMaybeInTx(final Channel channel, final boolean isKeepAlive, final Req req, final Object extra, TransactionMode txMode) throws Throwable { private Object handleReqMaybeInTx(final Channel channel, final boolean isKeepAlive, final Req req,
final Object extra, TransactionMode txMode) throws Throwable {


if (txMode != null && txMode != TransactionMode.NONE) { if (txMode != null && txMode != TransactionMode.NONE) {


final AtomicReference<Object> result = new AtomicReference<>(); final AtomicReference<Object> result = new AtomicReference<>();


JPA.transaction(new Runnable() { try {
@Override JPA.transaction(new Runnable() {
public void run() { @Override
try { public void run() {
result.set(handleReqAndPostProcess(channel, isKeepAlive, req, extra)); Object res = handleReqAndPostProcess(channel, isKeepAlive, req, extra);
} catch (Throwable e) {
throw U.rte("Error occurred inside the transactional web handler!", e); if (res instanceof Throwable) {
// throw to rollback
Throwable err = (Throwable) res;
throw U.rte("Error occurred inside the transactional web handler!", err);
}

result.set(res);
} }
} }, txMode == TransactionMode.READ_ONLY);
}, txMode == TransactionMode.READ_ONLY);
} catch (Throwable e) {
result.set(e);
}


return result.get(); return result.get();


Expand All @@ -262,7 +282,7 @@ public void run() {
} }


private Object wrap(final Channel channel, final boolean isKeepAlive, final Req req, final int index, private Object wrap(final Channel channel, final boolean isKeepAlive, final Req req, final int index,
final Object extra, final HttpWrapper[] wrappers) throws Exception { final Object extra, final HttpWrapper[] wrappers, final TransactionMode txMode) throws Exception {


HttpWrapper wrapper = wrappers[index]; HttpWrapper wrapper = wrappers[index];


Expand All @@ -280,9 +300,9 @@ public Object invokeAndTransformResult(Mapper<Object, Object> transformation) th


Object val; Object val;
if (next < wrappers.length) { if (next < wrappers.length) {
val = wrap(channel, isKeepAlive, req, next, extra, wrappers); val = wrap(channel, isKeepAlive, req, next, extra, wrappers, txMode);
} else { } else {
val = handleReqAndPostProcess(channel, isKeepAlive, req, extra); val = handleReqMaybeInTx(channel, isKeepAlive, req, extra, txMode);
} }


return transformation != null ? transformation.map(val) : val; return transformation != null ? transformation.map(val) : val;
Expand All @@ -309,36 +329,51 @@ private Object handleError(Req req, Throwable e) {


protected abstract Object handleReq(Channel ctx, boolean isKeepAlive, Req req, Object extra) throws Throwable; protected abstract Object handleReq(Channel ctx, boolean isKeepAlive, Req req, Object extra) throws Throwable;


private Object handleReqAndPostProcess(Channel ctx, boolean isKeepAlive, Req req, Object extra) throws Throwable { private Object handleReqAndPostProcess(Channel ctx, boolean isKeepAlive, Req req, Object extra) {
Object result = handleReq(ctx, isKeepAlive, req, extra); Object result;

try {
result = handleReq(ctx, isKeepAlive, req, extra);


if (result instanceof Results) { } catch (Throwable e) {
result = ((Results) result).all(); result = e;
} }


return result; return HandlerResultProcessor.INSTANCE.postProcessResult(req, result);
} }


public void complete(Channel ctx, boolean isKeepAlive, MediaType contentType, Req req, Object result) { public void complete(Channel ctx, boolean isKeepAlive, MediaType contentType, Req req, Object result) {


if (result == null || result instanceof NotFound) { U.must(result != null, "The post-processed result cannot be null!");
http.notFound(ctx, isKeepAlive, contentType, this, req); U.must(!(result instanceof Req), "The post-processed result cannot be a Req instance!");
return; // not found U.must(!(result instanceof Resp), "The post-processed result cannot be a Resp instance!");

if (result instanceof Throwable) {
handleError(req, (Throwable) result);
return;
} }


if (result instanceof HttpStatus) { if (result == HttpStatus.NOT_FOUND) {
complete(ctx, isKeepAlive, contentType, req, U.rte("HttpStatus result is not supported!")); http.notFound(ctx, isKeepAlive, contentType, this, req);
return; return;
} }


if (result instanceof Throwable) { if (result == HttpStatus.ERROR) {
handleError(req, (Throwable) result); complete(ctx, isKeepAlive, contentType, req, U.rte("Handler error!"));
return; return;
}


} else { if (result == HttpStatus.ASYNC) {
HttpUtils.resultToResponse(req, result); return;
} }


processNormalResult(req, result);
}

private void processNormalResult(Req req, Object result) {

HttpUtils.resultToResponse(req, result);

// the Req object will do the rendering // the Req object will do the rendering
if (!req.isAsync()) { if (!req.isAsync()) {
req.done(); req.done();
Expand Down
@@ -0,0 +1,87 @@
package org.rapidoid.http.handler;

/*-
* #%L
* rapidoid-http-fast
* %%
* Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

import org.rapidoid.RapidoidThing;
import org.rapidoid.datamodel.Results;
import org.rapidoid.http.HttpStatus;
import org.rapidoid.http.NotFound;
import org.rapidoid.http.Req;
import org.rapidoid.http.Resp;
import org.rapidoid.u.U;

import java.util.concurrent.Future;

public class HandlerResultProcessor extends RapidoidThing {

public static final HandlerResultProcessor INSTANCE = new HandlerResultProcessor();

@SuppressWarnings("unchecked")
public Object postProcessResult(Req req, Object result) {

if (result instanceof HttpStatus) {
return result;

} else if (result instanceof Req) {

U.must(req == result, "Unknown request instance was received as result!");

return reqToStatus(req, result);

} else if (result instanceof Resp) {

U.must(req != null && req.response() == result, "Unknown response instance was received as result!");

return reqToStatus(req, result);

} else if (result == null || result instanceof NotFound) { // not found
return HttpStatus.NOT_FOUND;

} else if ((result instanceof Future<?>) || (result instanceof org.rapidoid.concurrent.Future<?>)) { // async

if (req != null) {
req.async();
}

return HttpStatus.ASYNC;

} else if (result instanceof Results) {
return ((Results) result).all(); // fetch while still inside tx (potentially)

} else {
return result;
}

}

private HttpStatus reqToStatus(Req req, Object result) {
if (req.isAsync()) {

U.must(result instanceof HttpStatus || result instanceof Req || result instanceof Resp,
"Didn't expect a direct result from an asynchronous handler!");

return HttpStatus.ASYNC;
}

return HttpStatus.DONE;
}

}
Expand Up @@ -949,4 +949,8 @@ private boolean isCacheable() {
&& !hasToken(); && !hasToken();
} }


public boolean hasResponseAttached() {
return response != null;
}

} }
Expand Up @@ -514,12 +514,12 @@ byte[] renderToBytes() {
HttpUtils.postProcessResponse(this); // the response might have been changed, so post-process again HttpUtils.postProcessResponse(this); // the response might have been changed, so post-process again
return bytes; return bytes;


} else if (result() != null) {
return serializeResponseContent();

} else if (body() != null) { } else if (body() != null) {
return Msc.toBytes(body()); return Msc.toBytes(body());


} else if (result() != null) {
return serializeResponseContent();

} else { } else {
throw U.rte("There's nothing to render!"); throw U.rte("There's nothing to render!");
} }
Expand Down
Expand Up @@ -73,8 +73,8 @@ public void writeContentLengthHeader(Channel ctx, int len) {
impl.writeContentLengthHeader(ctx, len); impl.writeContentLengthHeader(ctx, len);
} }


public void writeAsJson(MaybeReq req, Channel ctx, int code, boolean isKeepAlive, Object value) { public void writeHttpResp(MaybeReq req, Channel ctx, boolean isKeepAlive, int code, MediaType contentType, Object value) {
impl.writeAsJson(req, ctx, code, isKeepAlive, value); impl.writeHttpResp(req, ctx, isKeepAlive, code, contentType, value);
} }


public void done(Req req) { public void done(Req req) {
Expand Down

0 comments on commit ea0e1d4

Please sign in to comment.