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}