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.Serializable;
import java.util.Map;
import java.util.concurrent.Future;
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) {

if (result instanceof Req) {
if (req != 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!");
}

} else {
} else if (!(result instanceof HttpStatus)) {
req.response().result(result);
}
}
Expand All @@ -273,23 +273,6 @@ public static BasicConfig zone(Req req) {
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) {
resp.cookie(TOKEN, token, "HttpOnly");
}
Expand Down
Expand Up @@ -4,10 +4,10 @@
import org.rapidoid.annotation.Since;
import org.rapidoid.annotation.TransactionMode;
import org.rapidoid.ctx.With;
import org.rapidoid.datamodel.Results;
import org.rapidoid.http.*;
import org.rapidoid.http.customize.Customization;
import org.rapidoid.http.impl.MaybeReq;
import org.rapidoid.http.impl.ReqImpl;
import org.rapidoid.http.impl.RouteOptions;
import org.rapidoid.http.impl.lowlevel.HttpIO;
import org.rapidoid.jpa.JPA;
Expand All @@ -17,7 +17,6 @@
import org.rapidoid.net.abstracts.Channel;
import org.rapidoid.security.Secure;
import org.rapidoid.u.U;
import org.rapidoid.util.Msc;
import org.rapidoid.util.TokenAuthData;

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

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

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

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

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

if (contentType == MediaType.JSON) {
HttpIO.INSTANCE.writeAsJson(maybeReq, ctx, 200, isKeepAlive, result);
} else {
HttpIO.INSTANCE.write200(maybeReq, ctx, isKeepAlive, contentType, Msc.toBytes(result));
if (result == HttpStatus.ASYNC) {
return HttpStatus.ASYNC;
}

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;
}

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

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

result = HttpUtils.postprocessResult(req, result);

} catch (Throwable 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) {

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

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

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();

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

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];

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

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

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;

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

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

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

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

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

if (result == null || result instanceof NotFound) {
http.notFound(ctx, isKeepAlive, contentType, this, req);
return; // not found
U.must(result != null, "The post-processed result cannot be null!");
U.must(!(result instanceof Req), "The post-processed result cannot be a Req instance!");
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) {
complete(ctx, isKeepAlive, contentType, req, U.rte("HttpStatus result is not supported!"));
if (result == HttpStatus.NOT_FOUND) {
http.notFound(ctx, isKeepAlive, contentType, this, req);
return;
}

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

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

processNormalResult(req, result);
}

private void processNormalResult(Req req, Object result) {

HttpUtils.resultToResponse(req, result);

// the Req object will do the rendering
if (!req.isAsync()) {
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();
}

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
return bytes;

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

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

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

} else {
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);
}

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

public void done(Req req) {
Expand Down

0 comments on commit ea0e1d4

Please sign in to comment.