001package org.xbib.elasticsearch.http.netty;
002
003import org.apache.lucene.util.BytesRef;
004import org.apache.lucene.util.UnicodeUtil;
005import org.elasticsearch.common.lease.Releasable;
006import org.elasticsearch.rest.RestResponse;
007import org.elasticsearch.rest.RestStatus;
008import org.elasticsearch.rest.support.RestUtils;
009import org.jboss.netty.buffer.ChannelBuffer;
010import org.jboss.netty.buffer.ChannelBuffers;
011import org.jboss.netty.channel.Channel;
012import org.jboss.netty.channel.ChannelFuture;
013import org.jboss.netty.channel.ChannelFutureListener;
014import org.jboss.netty.handler.codec.http.Cookie;
015import org.jboss.netty.handler.codec.http.CookieDecoder;
016import org.jboss.netty.handler.codec.http.CookieEncoder;
017import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
018import org.jboss.netty.handler.codec.http.HttpHeaders;
019import org.jboss.netty.handler.codec.http.HttpMethod;
020import org.jboss.netty.handler.codec.http.HttpResponseStatus;
021import org.jboss.netty.handler.codec.http.HttpVersion;
022import org.xbib.elasticsearch.common.netty.ReleaseChannelFutureListener;
023import org.xbib.elasticsearch.http.HttpChannel;
024
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029public class NettyHttpChannel extends HttpChannel {
030
031    private static final ChannelBuffer END_JSONP;
032
033    static {
034        BytesRef U_END_JSONP = new BytesRef();
035        UnicodeUtil.UTF16toUTF8(");", 0, ");".length(), U_END_JSONP);
036        END_JSONP = ChannelBuffers.wrappedBuffer(U_END_JSONP.bytes, U_END_JSONP.offset, U_END_JSONP.length);
037    }
038
039    private final NettyWebSocketServerTransport transport;
040
041    private final Channel channel;
042
043    private final org.jboss.netty.handler.codec.http.HttpRequest nettyRequest;
044
045
046    public NettyHttpChannel(NettyWebSocketServerTransport transport, Channel channel,
047                            NettyHttpRequest request) {
048        super(request);
049        this.transport = transport;
050        this.channel = channel;
051        this.nettyRequest = request.request();
052    }
053
054    @Override
055    public org.elasticsearch.common.io.stream.BytesStreamOutput newBytesOutput() {
056        return new org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput(transport.bigArrays);
057    }
058
059    @Override
060    public void sendResponse(RestResponse response) {
061        // Decide whether to close the connection or not.
062        boolean http10 = nettyRequest.getProtocolVersion().equals(HttpVersion.HTTP_1_0);
063        boolean close =
064                HttpHeaders.Values.CLOSE.equalsIgnoreCase(nettyRequest.headers().get(HttpHeaders.Names.CONNECTION)) ||
065                        (http10 && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(nettyRequest.headers().get(HttpHeaders.Names.CONNECTION)));
066
067        // Build the response object.
068        HttpResponseStatus status = getStatus(response.status());
069        org.jboss.netty.handler.codec.http.HttpResponse resp;
070        if (http10) {
071            resp = new DefaultHttpResponse(HttpVersion.HTTP_1_0, status);
072            if (!close) {
073                resp.headers().add(HttpHeaders.Names.CONNECTION, "Keep-Alive");
074            }
075        } else {
076            resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
077        }
078        if (RestUtils.isBrowser(nettyRequest.headers().get(HttpHeaders.Names.USER_AGENT))) {
079            // add support for cross origin
080            resp.headers().add("Access-Control-Allow-Origin", transport.settings().get("http.cors.allow-origin", "*"));
081            if (nettyRequest.getMethod() == HttpMethod.OPTIONS) {
082                // Allow Ajax requests based on the CORS "preflight" request
083                resp.headers().add("Access-Control-Max-Age", transport.settings().getAsInt("http.cors.max-age", 1728000));
084                resp.headers().add("Access-Control-Allow-Methods", transport.settings().get("http.cors.allow-methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE"));
085                resp.headers().add("Access-Control-Allow-Headers", transport.settings().get("http.cors.allow-headers", "X-Requested-With, Content-Type, Content-Length"));
086            }
087        }
088
089        String opaque = nettyRequest.headers().get("X-Opaque-Id");
090        if (opaque != null) {
091            resp.headers().add("X-Opaque-Id", opaque);
092        }
093
094        // Add all custom headers
095        Map<String, List<String>> customHeaders = response.getHeaders();
096        if (customHeaders != null) {
097            for (Map.Entry<String, List<String>> headerEntry : customHeaders.entrySet()) {
098                for (String headerValue : headerEntry.getValue()) {
099                    resp.headers().add(headerEntry.getKey(), headerValue);
100                }
101            }
102        }
103
104        ChannelBuffer buffer;
105        org.elasticsearch.common.bytes.BytesReference content = response.content();
106        boolean addedReleaseListener = false;
107        try {
108            buffer = response.contentThreadSafe() ?
109                    ChannelBuffers.wrappedBuffer(content.toBytes(), 0, content.length()) :
110                    ChannelBuffers.copiedBuffer(content.toBytes(), 0, content.length());
111
112            // handle JSONP
113            String callback = request.param("callback");
114            if (callback != null) {
115                final BytesRef callbackBytes = new BytesRef(callback.length() * 4 + 1);
116                UnicodeUtil.UTF16toUTF8(callback, 0, callback.length(), callbackBytes);
117                callbackBytes.bytes[callbackBytes.length] = '(';
118                callbackBytes.length++;
119                buffer = ChannelBuffers.wrappedBuffer(
120                        ChannelBuffers.wrappedBuffer(callbackBytes.bytes, callbackBytes.offset, callbackBytes.length),
121                        buffer,
122                        ChannelBuffers.wrappedBuffer(END_JSONP)
123                );
124            }
125            resp.setContent(buffer);
126            resp.headers().add(HttpHeaders.Names.CONTENT_TYPE, response.contentType());
127            resp.headers().add(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buffer.readableBytes()));
128
129            if (transport.resetCookies) {
130                String cookieString = nettyRequest.headers().get(HttpHeaders.Names.COOKIE);
131                if (cookieString != null) {
132                    CookieDecoder cookieDecoder = new CookieDecoder();
133                    Set<Cookie> cookies = cookieDecoder.decode(cookieString);
134                    if (!cookies.isEmpty()) {
135                        // Reset the cookies if necessary.
136                        CookieEncoder cookieEncoder = new CookieEncoder(true);
137                        for (Cookie cookie : cookies) {
138                            cookieEncoder.addCookie(cookie);
139                        }
140                        resp.headers().add(HttpHeaders.Names.SET_COOKIE, cookieEncoder.encode());
141                    }
142                }
143            }
144
145            ChannelFuture future = channel.write(resp);
146            if (response.contentThreadSafe() && content instanceof Releasable) {
147                future.addListener(new ReleaseChannelFutureListener((Releasable) content));
148                addedReleaseListener = true;
149            }
150            if (close) {
151                future.addListener(ChannelFutureListener.CLOSE);
152            }
153        } finally {
154            if (!addedReleaseListener && content instanceof Releasable) {
155                ((Releasable) content).close();
156            }
157        }
158    }
159
160    private HttpResponseStatus getStatus(RestStatus status) {
161        switch (status) {
162            case CONTINUE:
163                return HttpResponseStatus.CONTINUE;
164            case SWITCHING_PROTOCOLS:
165                return HttpResponseStatus.SWITCHING_PROTOCOLS;
166            case OK:
167                return HttpResponseStatus.OK;
168            case CREATED:
169                return HttpResponseStatus.CREATED;
170            case ACCEPTED:
171                return HttpResponseStatus.ACCEPTED;
172            case NON_AUTHORITATIVE_INFORMATION:
173                return HttpResponseStatus.NON_AUTHORITATIVE_INFORMATION;
174            case NO_CONTENT:
175                return HttpResponseStatus.NO_CONTENT;
176            case RESET_CONTENT:
177                return HttpResponseStatus.RESET_CONTENT;
178            case PARTIAL_CONTENT:
179                return HttpResponseStatus.PARTIAL_CONTENT;
180            case MULTI_STATUS:
181                // no status for this??
182                return HttpResponseStatus.INTERNAL_SERVER_ERROR;
183            case MULTIPLE_CHOICES:
184                return HttpResponseStatus.MULTIPLE_CHOICES;
185            case MOVED_PERMANENTLY:
186                return HttpResponseStatus.MOVED_PERMANENTLY;
187            case FOUND:
188                return HttpResponseStatus.FOUND;
189            case SEE_OTHER:
190                return HttpResponseStatus.SEE_OTHER;
191            case NOT_MODIFIED:
192                return HttpResponseStatus.NOT_MODIFIED;
193            case USE_PROXY:
194                return HttpResponseStatus.USE_PROXY;
195            case TEMPORARY_REDIRECT:
196                return HttpResponseStatus.TEMPORARY_REDIRECT;
197            case BAD_REQUEST:
198                return HttpResponseStatus.BAD_REQUEST;
199            case UNAUTHORIZED:
200                return HttpResponseStatus.UNAUTHORIZED;
201            case PAYMENT_REQUIRED:
202                return HttpResponseStatus.PAYMENT_REQUIRED;
203            case FORBIDDEN:
204                return HttpResponseStatus.FORBIDDEN;
205            case NOT_FOUND:
206                return HttpResponseStatus.NOT_FOUND;
207            case METHOD_NOT_ALLOWED:
208                return HttpResponseStatus.METHOD_NOT_ALLOWED;
209            case NOT_ACCEPTABLE:
210                return HttpResponseStatus.NOT_ACCEPTABLE;
211            case PROXY_AUTHENTICATION:
212                return HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED;
213            case REQUEST_TIMEOUT:
214                return HttpResponseStatus.REQUEST_TIMEOUT;
215            case CONFLICT:
216                return HttpResponseStatus.CONFLICT;
217            case GONE:
218                return HttpResponseStatus.GONE;
219            case LENGTH_REQUIRED:
220                return HttpResponseStatus.LENGTH_REQUIRED;
221            case PRECONDITION_FAILED:
222                return HttpResponseStatus.PRECONDITION_FAILED;
223            case REQUEST_ENTITY_TOO_LARGE:
224                return HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE;
225            case REQUEST_URI_TOO_LONG:
226                return HttpResponseStatus.REQUEST_URI_TOO_LONG;
227            case UNSUPPORTED_MEDIA_TYPE:
228                return HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE;
229            case REQUESTED_RANGE_NOT_SATISFIED:
230                return HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE;
231            case EXPECTATION_FAILED:
232                return HttpResponseStatus.EXPECTATION_FAILED;
233            case UNPROCESSABLE_ENTITY:
234                return HttpResponseStatus.BAD_REQUEST;
235            case LOCKED:
236                return HttpResponseStatus.BAD_REQUEST;
237            case FAILED_DEPENDENCY:
238                return HttpResponseStatus.BAD_REQUEST;
239            case INTERNAL_SERVER_ERROR:
240                return HttpResponseStatus.INTERNAL_SERVER_ERROR;
241            case NOT_IMPLEMENTED:
242                return HttpResponseStatus.NOT_IMPLEMENTED;
243            case BAD_GATEWAY:
244                return HttpResponseStatus.BAD_GATEWAY;
245            case SERVICE_UNAVAILABLE:
246                return HttpResponseStatus.SERVICE_UNAVAILABLE;
247            case GATEWAY_TIMEOUT:
248                return HttpResponseStatus.GATEWAY_TIMEOUT;
249            case HTTP_VERSION_NOT_SUPPORTED:
250                return HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED;
251            default:
252                return HttpResponseStatus.INTERNAL_SERVER_ERROR;
253        }
254    }
255}