blob: b2d99f19012c44dc19eac0303552c09cf6b2fd55 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.peeco.impl;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import javax.enterprise.context.control.RequestContextController;
import javax.enterprise.inject.spi.CDI;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.peeco.api.Request;
import org.apache.peeco.api.Response;
public class PeecoChannelHandler extends SimpleChannelInboundHandler<HttpObject>
{
private static final HttpDataFactory factory = new DefaultHttpDataFactory(16384L);
private static final Logger logger = Logger.getLogger(PeecoChannelHandler.class.getName());
private List<HttpHandlerInfo> httpHandlerInfos;
private RequestContextController requestContextController;
public PeecoChannelHandler(List<HttpHandlerInfo> httpHandlerInfos)
{
this.httpHandlerInfos = httpHandlerInfos;
this.requestContextController = CDI.current().select(RequestContextController.class).get();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
{
ctx.flush();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception
{
if (msg instanceof HttpRequest)
{
try
{
requestContextController.activate();
HttpRequest nettyRequest = (HttpRequest) msg;
logger.log(Level.FINE, "Request received: HttpMethod: " + nettyRequest.method() + "; URI: " + nettyRequest.uri());
HttpHandlerInfo info = PeecoUtils.getMatchingHandler(nettyRequest, httpHandlerInfos);
logger.log(Level.FINE, "Calling matching HttpHandler: " + info.annotation);
if (info == null)
{
throw new Exception("No matching HttpHandler found for incoming URI from Netty Request: " + nettyRequest.uri());
}
Request request = new Request(PeecoUtils.mapHttpMethod(nettyRequest.method()), nettyRequest.uri(), null);
parseHeaders(nettyRequest, request);
parseQueryParams(nettyRequest, request);
parseBodyParams(nettyRequest, request);
Object handlerParentBean = info.bean;
try
{
Object returnValue = info.method.invoke(handlerParentBean, request);
if (returnValue instanceof Response)
{
Response response = (Response) returnValue;
ctx.write(createNettyResponse(ctx, response, nettyRequest), ctx.voidPromise());
}
else if (returnValue instanceof CompletionStage)
{
CompletionStage<Response> completionStageResponse = (CompletionStage<Response>) returnValue;
completionStageResponse.thenAccept(response ->
{
try
{
ctx.write(createNettyResponse(ctx, response, nettyRequest))
.addListener((ChannelFutureListener) channelFuture ->
logger.log(Level.FINE, "CompletionStage<Response> is finished"));
}
catch (IOException ex)
{
//redundant catch as it's caught in createNettyResponse() already, but IDE requires to catch it here again
}
});
}
}
catch (Exception ex)
{
throw new RuntimeException("Failed to create Netty Response from given HttpHandler Response object, Netty ChannelHandlerContext and Netty Request.");
}
}
finally
{
requestContextController.deactivate();
}
}
}
public FullHttpResponse createNettyResponse(ChannelHandlerContext ctx, Response response, HttpRequest nettyRequest) throws IOException
{
ByteBuf nettyBuffer = ctx.alloc().buffer(response.output().available());
try
{
nettyBuffer.writeBytes(response.output(), response.output().available());
}
catch (IOException ex)
{
throw new IOException("Response output from HttpHandler could not be written to Netty ByteBuffer.");
}
FullHttpResponse nettyResponse = new DefaultFullHttpResponse(
nettyRequest.protocolVersion(),
HttpResponseStatus.OK,
nettyBuffer);
for (Map.Entry<String, List<String>> headers : response.headers().entrySet())
{
nettyResponse.headers().add(headers.getKey(), headers.getValue());
}
nettyResponse.headers()
.setInt(HttpHeaderNames.CONTENT_LENGTH, nettyResponse.content().readableBytes());
return nettyResponse;
}
protected void parseHeaders(HttpRequest nettyRequest, Request request)
{
for (Map.Entry<String, String> headers : nettyRequest.headers())
{
List<String> values = request.headers().computeIfAbsent(headers.getKey(), k -> new ArrayList<>());
List<String> nettyHeaderValues = Arrays.asList(headers.getValue().split(","));
for (String value : nettyHeaderValues)
{
values.add(value);
}
}
}
protected void parseBodyParams(HttpRequest nettyRequest, Request request)
{
if (nettyRequest.method().equals(HttpMethod.POST))
{
HttpPostRequestDecoder decoder = null;
try
{
decoder = new HttpPostRequestDecoder(factory, nettyRequest);
for (InterfaceHttpData data : decoder.getBodyHttpDatas())
{
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute)
{
Attribute attr = (Attribute) data;
List<String> values = request.bodyParameters().computeIfAbsent(data.getName(), k -> new ArrayList<>());
try
{
values.add(attr.getValue());
}
catch (IOException ex)
{
throw new RuntimeException("Failed to parse attribute values from Netty Request body to " + Request.class.toString());
}
}
}
}
finally
{
if (decoder != null)
{
decoder.destroy();
}
}
}
}
protected void parseQueryParams(HttpRequest nettyRequest, Request request)
{
QueryStringDecoder decoder = new QueryStringDecoder(nettyRequest.uri());
for (Map.Entry<String, List<String>> queryParameters : decoder.parameters().entrySet())
{
List<String> values = request.queryParameters().computeIfAbsent(queryParameters.getKey(), k -> new ArrayList<>());
for (String value : queryParameters.getValue())
{
values.add(value);
}
}
}
}