/* * Licensed to Elastic Search and Shay Banon under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. Elastic Search 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.elasticsearch.wares; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; import org.elasticsearch.node.internal.InternalNode; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.support.RestUtils; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.concurrent.CountDownLatch; /** * A servlet that can be used to dispatch requests to elasticsearch. A {@link Node} will be started, reading * config from either <tt>/WEB-INF/elasticsearch.json</tt> or <tt>/WEB-INF/elasticsearch.yml</tt> but, by defualt, * with its internal HTTP interface disabled. * <p/> * <p>The node is registered as a servlet context attribute under <tt>elasticsearchNode</tt> so its easily * accessible from other web resources if needed. * <p/> * <p>The servlet can be registered under a prefix URI, and it will automatically adjust to handle it. */ public class NodeServlet extends HttpServlet { public static String NODE_KEY = "elasticsearchNode"; public static String NAME_PREFIX = "org.elasticsearch."; protected Node node; protected RestController restController; @Override public void init() throws ServletException { getServletContext().log("Initializing elasticsearch Node '" + getServletName() + "'"); ImmutableSettings.Builder settings = ImmutableSettings.settingsBuilder(); InputStream resourceAsStream = getServletContext().getResourceAsStream("/WEB-INF/elasticsearch.json"); if (resourceAsStream != null) { settings.loadFromStream("/WEB-INF/elasticsearch.json", resourceAsStream); try { resourceAsStream.close(); } catch (IOException e) { // ignore } } resourceAsStream = getServletContext().getResourceAsStream("/WEB-INF/elasticsearch.yml"); if (resourceAsStream != null) { settings.loadFromStream("/WEB-INF/elasticsearch.yml", resourceAsStream); try { resourceAsStream.close(); } catch (IOException e) { // ignore } } Enumeration<String> enumeration = getServletContext().getAttributeNames(); while (enumeration.hasMoreElements()) { String key = enumeration.nextElement(); if (key.startsWith(NAME_PREFIX)) { Object attribute = getServletContext().getAttribute(key); if (attribute != null) attribute = attribute.toString(); settings.put(key.substring(NAME_PREFIX.length()), (String) attribute); } } if (settings.get("http.enabled") == null) { settings.put("http.enabled", false); } node = NodeBuilder.nodeBuilder().settings(settings).node(); restController = ((InternalNode) node).injector().getInstance(RestController.class); getServletContext().setAttribute(NODE_KEY, node); } @Override public void destroy() { if (node != null) { getServletContext().removeAttribute(NODE_KEY); node.close(); } } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletRestRequest request = new ServletRestRequest(req); ServletRestChannel channel = new ServletRestChannel(request, resp); try { restController.dispatchRequest(request, channel); channel.latch.await(); } catch (Exception e) { throw new IOException("failed to dispatch request", e); } if (channel.sendFailure != null) { throw channel.sendFailure; } } static class ServletRestChannel implements RestChannel { final RestRequest restRequest; final HttpServletResponse resp; final CountDownLatch latch; IOException sendFailure; ServletRestChannel(RestRequest restRequest, HttpServletResponse resp) { this.restRequest = restRequest; this.resp = resp; this.latch = new CountDownLatch(1); } @Override public void sendResponse(RestResponse response) { resp.setContentType(response.contentType()); if (RestUtils.isBrowser(restRequest.header("User-Agent"))) { resp.addHeader("Access-Control-Allow-Origin", "*"); if (restRequest.method() == RestRequest.Method.OPTIONS) { // also add more access control parameters resp.addHeader("Access-Control-Max-Age", "1728000"); resp.addHeader("Access-Control-Allow-Methods", "PUT, DELETE"); resp.addHeader("Access-Control-Allow-Headers", "X-Requested-With"); } } String opaque = restRequest.header("X-Opaque-Id"); if (opaque != null) { resp.addHeader("X-Opaque-Id", opaque); } try { int contentLength = response.contentLength(); if (response.prefixContent() != null) { contentLength += response.prefixContentLength(); } if (response.suffixContent() != null) { contentLength += response.suffixContentLength(); } resp.setContentLength(contentLength); ServletOutputStream out = resp.getOutputStream(); if (response.prefixContent() != null) { out.write(response.prefixContent(), 0, response.prefixContentLength()); } out.write(response.content(), 0, response.contentLength()); if (response.suffixContent() != null) { out.write(response.suffixContent(), 0, response.suffixContentLength()); } out.close(); } catch (IOException e) { sendFailure = e; } finally { latch.countDown(); } } } }