/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.fastcatsearch.http;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import org.fastcatsearch.http.action.ActionResponse;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class NettyHttpChannel implements HttpChannel {
private static final Logger logger = LoggerFactory.getLogger(NettyHttpChannel.class);
private final Channel channel;
private final HttpRequest request;
public NettyHttpChannel(Channel channel, HttpRequest request) {
this.channel = channel;
this.request = request;
}
@Override
public Channel channel() {
return channel;
}
@Override
public void sendHeader(ActionResponse response) {
// Decide whether to close the connection or not.
boolean http10 = request.getProtocolVersion().equals(HttpVersion.HTTP_1_0);
boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
|| (http10 && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION)));
// Build the response object.
HttpResponseStatus status = HttpResponseStatus.OK;
HttpResponse resp = null;
if (http10) {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_0, status);
if (!close) {
resp.addHeader(HttpHeaders.Names.CONNECTION, "Keep-Alive");
}
} else {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
}
if (response.responseCookie() != null) {
resp.addHeader(HttpHeaders.Names.COOKIE, response.responseCookie());
}
if (response.responseSetCookie() != null) {
resp.addHeader(HttpHeaders.Names.SET_COOKIE, response.responseSetCookie());
}
resp.setHeader(HttpHeaders.Names.CONTENT_TYPE, response.contentType());
}
@Override
public void close() {
boolean http10 = request.getProtocolVersion().equals(HttpVersion.HTTP_1_0);
boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
|| (http10 && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION)));
logger.debug("netty http close = {}", close);
if (close) {
channel.close();
}
}
@Override
public void sendResponse(ActionResponse response) {
// Decide whether to close the connection or not.
boolean http10 = request.getProtocolVersion().equals(HttpVersion.HTTP_1_0);
boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
|| (http10 && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION)));
// Build the response object.
HttpResponseStatus status = response.status();
HttpResponse resp = null;
if (http10) {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_0, status);
if (!close) {
resp.addHeader(HttpHeaders.Names.CONNECTION, "Keep-Alive");
}
} else {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
}
if (response.responseCookie() != null) {
resp.addHeader(HttpHeaders.Names.COOKIE, response.responseCookie());
}
if (response.responseSetCookie() != null) {
resp.addHeader(HttpHeaders.Names.SET_COOKIE, response.responseSetCookie());
}
if (!response.isEmpty()) {
ChannelBuffer buf = null;
if (response.contentThreadSafe()) {
buf = ChannelBuffers.wrappedBuffer(response.content(), response.contentOffset(), response.contentLength());
} else {
buf = ChannelBuffers.copiedBuffer(response.content(), response.contentOffset(), response.contentLength());
}
//
resp.setContent(buf);
resp.setHeader(HttpHeaders.Names.CONTENT_TYPE, response.contentType());
resp.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buf.readableBytes()));
}
// Write the response.
ChannelFuture future = channel.write(resp);
// Close the connection after the write operation is done if necessary.
if (close) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void sendError(HttpResponseStatus status, Throwable throwable) {
// Decide whether to close the connection or not.
boolean http10 = request.getProtocolVersion().equals(HttpVersion.HTTP_1_0);
boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION))
|| (http10 && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(HttpHeaders.Names.CONNECTION)));
// Build the response object.
HttpResponse resp = null;
if (http10) {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_0, status);
if (!close) {
resp.addHeader(HttpHeaders.Names.CONNECTION, "Keep-Alive");
}
} else {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
}
String cause = getErrorHtml(status, throwable);
byte[] errorMessage = cause.getBytes(Charset.forName("UTF-8"));
ChannelBuffer buf = ChannelBuffers.wrappedBuffer(errorMessage);
resp.setContent(buf);
resp.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/html");
resp.setHeader(HttpHeaders.Names.CONTENT_ENCODING, "UTF-8");
resp.setHeader(HttpHeaders.Names.CONTENT_LENGTH, buf.readableBytes());
// Write the response.
ChannelFuture future = channel.write(resp);
// Close the connection after the write operation is done if necessary.
if (close) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
private String getErrorHtml(HttpResponseStatus status, Throwable throwable) {
String stackTrace = null;
if (throwable != null) {
StringWriter sw = new StringWriter();
PrintWriter s = new PrintWriter(sw);
throwable.printStackTrace(s);
stackTrace = sw.toString();
}
return "<html>\n" + "<head>\n" + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n" + "<title>Error " + status.toString() + "</title>\n"
+ "</head>\n" + "<body>\n" + "<h2>HTTP ERROR: " + status.getCode() + "</h2>\n" + "<p>Problem accessing [" + request.getMethod() + "]" + request.getUri()
+ ". Reason:\n" + "<pre> " + status.getReasonPhrase() + (stackTrace != null ? "\n\n" + stackTrace : "") + "</pre></p>\n"
+ "<hr /><i><small>Powered by FastcatSearch</small></i>\n" + "</body>\n" + "</html>";
}
}