Java tutorial
/* * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * Licensed under the Eclipse Public License version 1.0, available at http://www.eclipse.org/legal/epl-v10.html */ package io.liveoak.container.protocols.http; import java.util.List; import io.liveoak.common.DefaultResourceErrorResponse; import io.liveoak.common.DefaultResourceRequest; import io.liveoak.common.DefaultResourceResponse; import io.liveoak.common.codec.EncodingResult; import io.liveoak.common.codec.IncompatibleMediaTypeException; import io.liveoak.common.codec.ResourceCodecManager; import io.liveoak.container.protocols.RequestCompleteEvent; import io.liveoak.container.tenancy.InternalApplication; import io.liveoak.spi.Application; import io.liveoak.spi.MediaType; import io.liveoak.spi.MediaTypeMatcher; import io.liveoak.spi.RequestContext; import io.liveoak.spi.RequestType; import io.liveoak.spi.ResourceErrorResponse; import io.liveoak.spi.ResourceMovedResponse; import io.liveoak.spi.ResourcePath; import io.liveoak.spi.ResourceRequest; import io.liveoak.spi.ResourceResponse; import io.liveoak.spi.resource.async.BinaryContentSink; import io.liveoak.spi.resource.async.BinaryResource; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.jboss.logging.Logger; /** * @author Bob McWhirter */ public class HttpResourceResponseEncoder extends MessageToMessageEncoder<DefaultResourceResponse> { public HttpResourceResponseEncoder(ResourceCodecManager codecManager) { this.codecManager = codecManager; } @Override protected void encode(ChannelHandlerContext ctx, DefaultResourceResponse msg, List<Object> out) throws Exception { int responseStatusCode = 0; String responseMessage = null; HttpHeaders responseHeaders = new DefaultHttpHeaders(); boolean shouldEncodeState = false; boolean shouldCheckForHtmlApp = true; switch (msg.responseType()) { case CREATED: responseStatusCode = HttpResponseStatus.CREATED.code(); responseMessage = HttpResponseStatus.CREATED.reasonPhrase(); shouldEncodeState = true; break; case READ: responseStatusCode = HttpResponseStatus.OK.code(); responseMessage = HttpResponseStatus.OK.reasonPhrase(); shouldEncodeState = true; break; case UPDATED: responseStatusCode = HttpResponseStatus.OK.code(); responseMessage = HttpResponseStatus.OK.reasonPhrase(); shouldEncodeState = true; break; case DELETED: responseStatusCode = HttpResponseStatus.OK.code(); responseMessage = HttpResponseStatus.OK.reasonPhrase(); shouldEncodeState = true; break; case ERROR: if (msg instanceof ResourceErrorResponse) { shouldEncodeState = true; shouldCheckForHtmlApp = false; switch (((DefaultResourceErrorResponse) msg).errorType()) { case NOT_AUTHORIZED: responseStatusCode = HttpResponseStatus.UNAUTHORIZED.code(); responseMessage = HttpResponseStatus.UNAUTHORIZED.reasonPhrase(); break; case FORBIDDEN: responseStatusCode = HttpResponseStatus.FORBIDDEN.code(); responseMessage = HttpResponseStatus.FORBIDDEN.reasonPhrase(); break; case NOT_ACCEPTABLE: responseStatusCode = HttpResponseStatus.NOT_ACCEPTABLE.code(); responseMessage = HttpResponseStatus.NOT_ACCEPTABLE.reasonPhrase(); break; case NO_SUCH_RESOURCE: responseStatusCode = HttpResponseStatus.NOT_FOUND.code(); responseMessage = HttpResponseStatus.NOT_FOUND.reasonPhrase(); break; case RESOURCE_ALREADY_EXISTS: responseStatusCode = HttpResponseStatus.NOT_ACCEPTABLE.code(); responseMessage = HttpResponseStatus.NOT_ACCEPTABLE.reasonPhrase(); break; case CREATE_NOT_SUPPORTED: responseStatusCode = HttpResponseStatus.METHOD_NOT_ALLOWED.code(); responseMessage = "Create not supported"; break; case READ_NOT_SUPPORTED: responseStatusCode = HttpResponseStatus.METHOD_NOT_ALLOWED.code(); responseMessage = "Read not supported"; break; case UPDATE_NOT_SUPPORTED: responseStatusCode = HttpResponseStatus.METHOD_NOT_ALLOWED.code(); responseMessage = "UpdateStep not supported"; break; case DELETE_NOT_SUPPORTED: responseStatusCode = HttpResponseStatus.METHOD_NOT_ALLOWED.code(); responseMessage = "Delete not supported"; break; case INTERNAL_ERROR: responseStatusCode = HttpResponseStatus.INTERNAL_SERVER_ERROR.code(); responseMessage = HttpResponseStatus.INTERNAL_SERVER_ERROR.reasonPhrase(); break; } //TODO: add content values here to return proper error messages to the client // eg unique error id, short error message, link to page with more information, etc... //response.content().writeBytes(...) } break; case MOVED: if (msg instanceof ResourceMovedResponse) { ResourceMovedResponse resourceMovedResponse = (ResourceMovedResponse) msg; switch (resourceMovedResponse.movedType()) { case MOVED_PERMANENTLY: responseStatusCode = HttpResponseStatus.MOVED_PERMANENTLY.code(); responseMessage = HttpResponseStatus.MOVED_PERMANENTLY.reasonPhrase(); break; case MOVED_TEMPORARILY: responseStatusCode = HttpResponseStatus.FOUND.code(); responseMessage = HttpResponseStatus.FOUND.reasonPhrase(); break; } Integer maxAge = resourceMovedResponse.maxAge(); if (maxAge != null) { responseHeaders.add(HttpHeaders.Names.CACHE_CONTROL, "max-age=" + maxAge); } responseHeaders.add(HttpHeaders.Names.LOCATION, ((ResourceMovedResponse) msg).redirectURL()); } break; } DefaultHttpResponse response; HttpResponseStatus responseStatus; EncodingResult encodingResult = null; if (shouldEncodeState) { MediaTypeMatcher matcher = msg.inReplyTo().mediaTypeMatcher(); if (shouldCheckForHtmlApp) { Application app = msg.inReplyTo().requestContext().application(); if (app != null && app instanceof InternalApplication) { ResourcePath htmlAppPath = ((InternalApplication) app).htmlApplicationResourcePath(); if ((!(msg.resource() instanceof BinaryResource)) && (htmlAppPath != null)) { MediaType bestMatch = matcher.findBestMatch(this.codecManager.mediaTypes()); if (bestMatch == MediaType.HTML) { // HTML was requested and we have an HTML app ResourceRequest htmlAppRequest = new DefaultResourceRequest.Builder(RequestType.READ, htmlAppPath).mediaTypeMatcher(msg.inReplyTo().mediaTypeMatcher()) .requestAttributes(msg.inReplyTo().requestContext().requestAttributes()) .build(); ctx.channel().pipeline().fireChannelRead(htmlAppRequest); return; } } } } try { encodingResult = encodeState(msg.inReplyTo().requestContext(), matcher, msg); } catch (IncompatibleMediaTypeException e) { log.error("Incompatible media type", e); responseStatus = new HttpResponseStatus(HttpResponseStatus.NOT_ACCEPTABLE.code(), e.getMessage()); response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus); response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, 0); out.add(response); return; } catch (Throwable e) { log.error("Could not encode HTTP response", e); responseStatus = new HttpResponseStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), HttpResponseStatus.INTERNAL_SERVER_ERROR.reasonPhrase()); response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus); response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, 0); out.add(response); return; } } responseStatus = new HttpResponseStatus(responseStatusCode, responseMessage); if (encodingResult != null) { if (msg.resource() instanceof BinaryResource) { BinaryResource bin = (BinaryResource) msg.resource(); if (bin.contentLength() == 0) { response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus); response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, 0); } else { response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, responseStatus); response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, bin.contentLength()); response.headers().add(HttpHeaders.Names.LOCATION, msg.resource().uri().toString()); response.headers().add(HttpHeaders.Names.CONTENT_TYPE, bin.mediaType()); final HttpResponse res = response; bin.readContent(msg.inReplyTo().requestContext(), new BinaryContentSink() { { ctx.write(res); } @Override public void close() { ctx.writeAndFlush(new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER)); ctx.pipeline().fireUserEventTriggered(new RequestCompleteEvent(msg.requestId())); } @Override public void accept(ByteBuf byteBuf) { ctx.write(new DefaultHttpContent(byteBuf)); } }); return; } } else { ByteBuf content = encodingResult.encoded(); response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus, content); response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes()); if (msg.resource() != null) { response.headers().add(HttpHeaders.Names.LOCATION, msg.resource().uri().toString()); } else { response.headers().add(HttpHeaders.Names.LOCATION, msg.inReplyTo().resourcePath().toString()); } response.headers().add(HttpHeaders.Names.CONTENT_TYPE, encodingResult.mediaType()); } } else { response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus); response.headers().add(HttpHeaders.Names.CONTENT_LENGTH, 0); } response.headers().add(responseHeaders); out.add(response); ctx.fireUserEventTriggered(new RequestCompleteEvent(msg.requestId())); } protected EncodingResult encodeState(RequestContext ctx, MediaTypeMatcher mediaTypeMatcher, ResourceResponse response) throws Exception { return this.codecManager.encode(ctx, mediaTypeMatcher, response); } private ResourceCodecManager codecManager; private static final Logger log = Logger.getLogger(HttpResourceResponseEncoder.class); }