Skip to content

Commit

Permalink
#153 Serializing the result into a HTTP response body inside tx.
Browse files Browse the repository at this point in the history
  • Loading branch information
nmihajlovski committed Dec 21, 2017
1 parent 6073819 commit 8396c33
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 67 deletions.
Expand Up @@ -399,6 +399,7 @@ org.rapidoid.http.handler.PredefinedResponseHandler
org.rapidoid.http.handler.ResourceHttpHandler
org.rapidoid.http.handler.StaticHttpHandler
org.rapidoid.http.handler.StaticResourcesHandler
org.rapidoid.http.handler.TxUtil
org.rapidoid.http.Headers
org.rapidoid.http.HTTP
org.rapidoid.http.HttpClient
Expand Down Expand Up @@ -434,6 +435,7 @@ org.rapidoid.http.impl.MVCModelImpl
org.rapidoid.http.impl.PathPattern
org.rapidoid.http.impl.RapidoidReqInfo
org.rapidoid.http.impl.ReqImpl
org.rapidoid.http.impl.RespBodyBytes
org.rapidoid.http.impl.RespImpl
org.rapidoid.http.impl.ResponseRenderer
org.rapidoid.http.impl.RouteCacheConfig
Expand All @@ -452,6 +454,7 @@ org.rapidoid.http.Req
org.rapidoid.http.ReqHandler
org.rapidoid.http.ReqRespHandler
org.rapidoid.http.Resp
org.rapidoid.http.RespBody
org.rapidoid.http.REST
org.rapidoid.http.RESTClient
org.rapidoid.http.RESTResultMapper
Expand Down
Expand Up @@ -7,9 +7,9 @@
* 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.
Expand Down
Expand Up @@ -291,7 +291,7 @@ private void internalServerError(final Channel channel, final boolean isKeepAliv
MediaType contentType = req != null ? req.contentType() : HttpUtils.getDefaultContentType();

HttpResponseRenderer jsonRenderer = Customization.of(req).jsonResponseRenderer();
byte[] body = HttpUtils.responseToBytes(req, INTERNAL_SERVER_ERROR, contentType, jsonRenderer);
RespBody body = new RespBodyBytes(HttpUtils.responseToBytes(req, INTERNAL_SERVER_ERROR, contentType, jsonRenderer));

HttpIO.INSTANCE.respond(HttpUtils.maybe(req), channel, -1, -1, 500, isKeepAlive, contentType, body, null, null);
}
Expand Down
32 changes: 32 additions & 0 deletions rapidoid-http-fast/src/main/java/org/rapidoid/http/RespBody.java
@@ -0,0 +1,32 @@
/*-
* #%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%
*/

package org.rapidoid.http;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;

@Authors("Nikolche Mihajlovski")
@Since("5.5.1")
public interface RespBody {

byte[] toBytes();

}
Expand Up @@ -28,6 +28,7 @@
import org.rapidoid.http.customize.Customization;
import org.rapidoid.http.impl.MaybeReq;
import org.rapidoid.http.impl.ReqImpl;
import org.rapidoid.http.impl.RespImpl;
import org.rapidoid.http.impl.RouteOptions;
import org.rapidoid.http.impl.lowlevel.HttpIO;
import org.rapidoid.jpa.JPA;
Expand Down Expand Up @@ -139,21 +140,12 @@ private Set<String> userRoles(Req req, String username) {
}
}

private TransactionMode before(final Req req, String username, Set<String> roles) {

private void before(final Req req, String username, Set<String> roles) {
if (U.notEmpty(options.roles()) && !Secure.hasAnyRole(username, roles, options.roles())) {
throw new SecurityException("The user doesn't have the required roles!");
}

req.response().view(options.view()).contentType(options.contentType()).mvc(options.mvc());

TransactionMode txMode = U.or(options.transaction(), TransactionMode.NONE);

if (txMode == TransactionMode.AUTO) {
txMode = HttpUtils.isGetReq(req) ? TransactionMode.READ_ONLY : TransactionMode.READ_WRITE;
}

return txMode;
}

private void execHandlerJob(final Channel channel, final boolean isKeepAlive, final MediaType contentType,
Expand All @@ -180,7 +172,9 @@ public void run() {
roles = userRoles(req, username);
scope = auth != null ? auth.scope : null;

TransactionMode txMode = before(req, username, roles);
before(req, username, roles);

TransactionMode txMode = TxUtil.txModeOf(req, options.transaction());
U.notNull(txMode, "txMode");

HttpWrapper[] wrappers = httpWrappers != null ? httpWrappers : U.or(Customization.of(req).wrappers(), NO_WRAPPERS);
Expand Down Expand Up @@ -234,7 +228,7 @@ public void run() {
}

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

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

Expand All @@ -250,6 +244,12 @@ public void run() {
// throw to rollback
Throwable err = (Throwable) res;
throw U.rte("Error occurred inside the transactional web handler!", err);

} else {
// serialize the result into a HTTP response body, while still inside tx (see #153)
RespImpl resp = (RespImpl) req.response(); // TODO find cleaner access
RespBody body = resp.resultToRespBody(res);
res = body;
}

result.set(res);
Expand All @@ -267,21 +267,6 @@ public void run() {
}
}

private Runnable txWrap(final TransactionMode txMode, final Runnable handleRequest) {
if (txMode != null && txMode != TransactionMode.NONE) {

return new Runnable() {
@Override
public void run() {
JPA.transaction(handleRequest, txMode == TransactionMode.READ_ONLY);
}
};

} else {
return handleRequest;
}
}

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

Expand Down
@@ -0,0 +1,47 @@
/*-
* #%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%
*/

package org.rapidoid.http.handler;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.annotation.TransactionMode;
import org.rapidoid.http.HttpUtils;
import org.rapidoid.http.Req;
import org.rapidoid.u.U;


@Authors("Nikolche Mihajlovski")
@Since("5.5.1")
public class TxUtil extends RapidoidThing {

static TransactionMode txModeOf(Req req, TransactionMode configuredTxMode) {
TransactionMode txMode = U.or(configuredTxMode, TransactionMode.NONE);

if (txMode == TransactionMode.AUTO) {
txMode = HttpUtils.isGetReq(req) ? TransactionMode.READ_ONLY : TransactionMode.READ_WRITE;
}

return txMode;
}


}
Expand Up @@ -446,17 +446,17 @@ public synchronized Resp response() {
return response;
}

void doRendering(int code, byte[] responseBody) {
void doRendering(int code, RespBody body) {
if (!isRendering()) {
synchronized (this) {
if (!isRendering()) {
respond(code, responseBody);
respond(code, body);
}
}
}
}

private void respond(int code, byte[] responseBody) {
private void respond(int code, RespBody body) {
MediaType contentType = HttpUtils.getDefaultContentType();

if (tokenChanged.get()) {
Expand All @@ -471,16 +471,16 @@ private void respond(int code, byte[] responseBody) {
contentType = U.or(response.contentType(), contentType);
}

renderResponse(code, contentType, responseBody);
renderResponse(code, contentType, body);
}

private void renderResponse(int code, MediaType contentType, byte[] responseBody) {
private void renderResponse(int code, MediaType contentType, RespBody body) {
rendering = true;
completed = responseBody != null;
completed = body != null;

HttpIO.INSTANCE.respond(
HttpUtils.maybe(this), channel, connId, handle,
code, isKeepAlive, contentType, responseBody,
code, isKeepAlive, contentType, body,
response != null ? response.headers() : null,
response != null ? response.cookies() : null
);
Expand Down Expand Up @@ -544,7 +544,7 @@ private void renderResponseOrError() {
String err = validateResponse();

if (err != null) {
doRendering(500, err.getBytes());
doRendering(500, new RespBodyBytes(err.getBytes()));

} else {
renderResponse();
Expand All @@ -566,27 +566,27 @@ private void renderResponse() {
HttpIO.INSTANCE.done(this);

} else {
// first serialize the response to bytes (with error handling)
byte[] bytes = responseToBytes();
// first create a response body from the result (with error handling)
RespBody body = toRespBody();

// then rendering
doRendering(response.code(), bytes);
// then render the response
doRendering(response.code(), body);
}
}

private byte[] responseToBytes() {
private RespBody toRespBody() {
try {
return response.renderToBytes();
return response.createRespBodyFromResult();

} catch (Throwable e) {
HttpIO.INSTANCE.error(this, e, LogLevel.ERROR);

try {
return response.renderToBytes();
return response.createRespBodyFromResult();

} catch (Exception e1) {
Log.error("Internal rendering error!", e1);
return HttpUtils.getErrorMessageAndSetCode(response, e1).getBytes();
return new RespBodyBytes(HttpUtils.getErrorMessageAndSetCode(response, e1).getBytes());
}
}
}
Expand Down
@@ -0,0 +1,43 @@
/*-
* #%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%
*/

package org.rapidoid.http.impl;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.http.RespBody;

@Authors("Nikolche Mihajlovski")
@Since("5.5.1")
public class RespBodyBytes extends RapidoidThing implements RespBody {

private final byte[] bytes;

public RespBodyBytes(byte[] bytes) {
this.bytes = bytes;
}

@Override
public byte[] toBytes() {
return bytes;
}

}
Expand Up @@ -30,10 +30,7 @@
import org.rapidoid.config.Conf;
import org.rapidoid.ctx.Ctxs;
import org.rapidoid.ctx.UserInfo;
import org.rapidoid.http.HttpUtils;
import org.rapidoid.http.MediaType;
import org.rapidoid.http.Req;
import org.rapidoid.http.Resp;
import org.rapidoid.http.*;
import org.rapidoid.http.customize.Customization;
import org.rapidoid.http.customize.HttpResponseRenderer;
import org.rapidoid.http.customize.LoginProvider;
Expand Down Expand Up @@ -510,25 +507,30 @@ public String toString() {
'}';
}

byte[] renderToBytes() {
if (mvc()) {
RespBody createRespBodyFromResult() {
Object result = result();

if (result instanceof RespBody) {
return (RespBody) result;

} else if (mvc()) {
byte[] bytes = ResponseRenderer.renderMvc(req, this);
HttpUtils.postProcessResponse(this); // the response might have been changed, so post-process again
return bytes;
return new RespBodyBytes(bytes);

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

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

} else {
throw U.rte("There's nothing to render!");
}
}

private byte[] serializeResponseContent() {
return HttpUtils.responseToBytes(req, result(), contentType(), mediaResponseRenderer());
public RespBody resultToRespBody(Object result) {
return new RespBodyBytes(HttpUtils.responseToBytes(req, result, contentType(), mediaResponseRenderer()));
}

@Override
Expand Down Expand Up @@ -597,4 +599,5 @@ private HttpResponseRenderer mediaResponseRenderer() {
return Customization.of(req).jsonResponseRenderer();
}
}

}

0 comments on commit 8396c33

Please sign in to comment.