Jump to: navigation, search

SMILA/Documentation/JettyHttpServer

Configuration and Usage of the Jetty HTTP server embedded in SMILA

Overview

The embedding of the Jetty server is implemented in bundle org.eclipse.smila.http.server. When the bundle is activated it starts the OSGi service org.eclipse.smila.http.server.HttpService which in turn creates a Jetty server from a configuration file, adds request handlers provided by other OSGi services and starts the server.

Configuration

To configure the embedded Jetty server, place a file named jetty.xml in the configuration directory configuration/org.eclipse.smila.http.server. If the configuration area does not contain such a file, a default file provided by the HTTP server bundle itself is used. It's probably the most simple file possible:

<Configure id="Server" class="org.eclipse.jetty.server.Server">
 
    <!-- =========================================================== -->
    <!-- Server Thread Pool                                          -->
    <!-- =========================================================== -->
    <Set name="ThreadPool">
      <!-- Default queued blocking threadpool -->
      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
        <Set name="minThreads">10</Set>
        <Set name="maxThreads">200</Set>
        <Set name="detailedDump">false</Set>
      </New>
    </Set>
 
    <!-- =========================================================== -->
    <!-- Set connectors                                              -->
    <!-- =========================================================== -->
 
    <Call name="addConnector">
      <Arg>
          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
            <Set name="host"><Property name="jetty.host" /></Set>
            <Set name="port"><Property name="jetty.port" /></Set>
            <Set name="maxIdleTime">300000</Set>
            <Set name="Acceptors">2</Set>
            <Set name="statsOn">false</Set>
            <Set name="confidentialPort">8443</Set>
	    <Set name="lowResourcesConnections">20000</Set>
	    <Set name="lowResourcesMaxIdleTime">5000</Set>
          </New>
      </Arg>
    </Call>
 
    <!-- =========================================================== -->
    <!-- Set handler Collection Structure                            --> 
    <!-- =========================================================== -->
    <Set name="handler">
      <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
    </Set>
 
    <!-- =========================================================== -->
    <!-- extra options                                               -->
    <!-- =========================================================== -->
    <Set name="stopAtShutdown">true</Set>
    <Set name="sendServerVersion">true</Set>
    <Set name="sendDateHeader">true</Set>
    <Set name="gracefulShutdown">1000</Set>
    <Set name="dumpAfterStart">false</Set>
    <Set name="dumpBeforeStop">false</Set>
</Configure>

The default configuration is not especially useful by itself. It basically configures the server to listen at port 8080, adds a default handler responding with HTTP status 404 (NOT FOUND) if no other handler was found to handle the request, and lists the available handlers.

For more details on all the configuration properties used here, refer to the Jetty documentation next door.

Setting the HTTP port

Usually, the port on which the Jetty server will listen is configured via the ClusterConfig service. This means that in a standard SMILA setup you set the HTTP port in the configuration file configuration/org.eclipse.smila.clusterconfig.simple/clusterconfig.json which will set the configuration property jetty.port on startup of the HTTP server and thus override the default value given for this property in jetty.xml.

Example snippet from the clusterconfig.json file:

  "services":{
    "smila":{
      "httpPort":8080
    }
  }

To be exact: the service name will be determined by the serviceName property of the HTTP service component definition for the service implementation org.eclipse.smila.http.server.internal.HttpServiceImpl. If (and only if) this property and the ClusterConfigService are both available, the ClusterConfigService service will be called to determine the configured http port for the service by calling its int getHttpPort(final String serviceName) method. If the result is not -1 it will be set as the HTTP port for the HttpService. Otherwise the port as defined in the jetty.xml file will be used.

Usage

Handlers can be added using two different approaches: Either by extending the jetty.xml file, or by registering different kinds of OSGi services that are referenced by the HTTP server service and are registered at startup.

Before we dive into the details, first some important advices:

  • To implement functionality, you will probably provide new classes in your own bundle. If the HTTP server needs to instantiate these classes (e.g. servlets used by a web application), you must register your bundle as a "buddy" to the server bundle. So, if you have problems deploying your own code and if you get class loading related errors, first check if your MANIFEST.MF contains this line:
Eclipse-RegisterBuddy: org.eclipse.smila.http.server
  • You also must add these two imports to the manifest, otherwise the handlers will not be registered with the http server, despite being instantiated:
org.eclipse.smila.http.server, org.eclipse.smila.http.server.util
  • The Jetty server must be stopped and restarted to register additional handlers. Therefore, if you use OSGi services to register functionality in the server, you should take care that the HTTP server bundle is started in a higher run level than all bundles providing handler services. This is the reason why the HTTP server is started on the highest run level of all bundles in the SMILA application. A nice side effect of this is that you can check from outside if the startup of SMILA has finished: If you can connect to the HTTP server, this means that SMILA is up and running:
..., \
org.eclipse.smila.http.server@5:start
  • Also, you should not add handler services to the HTTP server bundle itself using Declarative Services, because the start order of DS services in a single bundle is not deterministic.

Deployment via jetty.xml

The straightforward way is to use the jetty.xml configuration file itself to add handlers for different URL contexts. In theory, it should be possible to do everything you can normally do in this configuration file, see the Jetty documentation for details. However, there may be some class loading fun ahead.

The default configuration file used in the SMILA application replaces the "handler" section of the default configuration to deploy a simple web application for search at http://localhost:8080/SMILA/search:

<!-- =========================================================== -->
<!-- Set handler Collection Structure                            --> 
<!-- =========================================================== -->
<Set name="handler">
  <New class="org.eclipse.jetty.server.handler.HandlerList">
    <Set name="handlers">
      <Array type="org.eclipse.jetty.server.Handler">
        <Item>
          <New class="org.eclipse.jetty.webapp.WebAppContext">
            <Set name="contextPath">/SMILA</Set>
            <Set name="resourceBase">configuration/org.eclipse.smila.search.servlet/webapp</Set>
            <Set name="descriptor">configuration/org.eclipse.smila.search.servlet/webapp/WEB-INF/web.xml</Set>
            <Set name="defaultsDescriptor">configuration/org.eclipse.smila.http.server/webdefault.xml</Set>
            <Set name="parentLoaderPriority">true</Set>
          </New>
        </Item>
        <Item> <!-- this one is always at the end of the list -->
          <New class="org.eclipse.jetty.server.handler.DefaultHandler"/>
        </Item>
      </Array>
    </Set>
  </New>
</Set>

It registers a standard web application located at configuration/org.eclipse.smila.search.servlet/webapp. Note that you must also specify the defaultsDescriptor property because the embedded Jetty cannot find one at the default location.

Example: Adding an extra resource handler to serve static files. This makes files in directory /home/smila/Images accessible at http://.../Images/:

<Set name="handler">
  <New class="org.eclipse.jetty.server.handler.HandlerList">
    <Set name="handlers">
      <Array type="org.eclipse.jetty.server.Handler">
 
        <!-- ... other handlers ... -->
 
        <Item>
          <New class="org.eclipse.jetty.server.handler.ContextHandler">
            <Set name="contextPath">/Images</Set>
            <Set name="handler">
              <New class="org.eclipse.jetty.server.handler.ResourceHandler">
                <Set name="directoriesListed">true</Set>
                <Set name="resourceBase">/home/smila/Images</Set>
              </New>
           </Set>
        </New>
      </Item>
      <Item> <!-- this one is always at the end of the list -->
        <New class="org.eclipse.jetty.server.handler.DefaultHandler"/>
      </Item>
    </Array>
  </New>
</Set>

HttpHandler services

org.eclipse.smila.http.server.HttpHandler is the most primitive SMILA specific interface for OSGi services for handling HTTP requests. It defines two methods:

  • String getRootContextPath(): return the context path (i.e. the part of the URL after the "http://<host>:<port>" stuff) for which this handler should be invoked. This is used by the HTTP server to select an appropriate handler for a request. The value must be a valid Jetty context path: The handler should be invoked for all requests to URI paths that start with this value.
  • void handle(final HttpExchange exchange) throws IOException: Actually handle the request. The HttpExchange object gives access to the HTTP method, request URI, request headers and input stream as well as response headers and output stream.

See org.eclipse.smila.http.server.test.MockHttpHandler in the HTTP server test bundle for the most simple implementation of this services. OSGI-INF/httphandler.xml shows how to start such a server using DS.

RequestDispatcher and RequestHandler services

The org.eclipse.smila.http.server.util.RequestDispatcher is a HttpHandler implementation that uses org.eclipse.smila.http.server.util.RequestHandler services to handle requests for its root context. RequestHandlers are HttpHandlers but cannot register for handling URIs containing prefixes. Instead, the dispatcher calls matches(String requestUri) and the RequestHandler can check programmatically if it should handle this request. Also, the dispatcher can add and remove request handlers without having to restart the complete HTTP server. To use a RequestDispatcher, you must start it using a DS descriptor. E.g. see the example in the ...http.server.test bundle in OSGI-INF/requestdispatcher.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component name="org.eclipse.smila.http.server.util.RequestDispatcher" immediate="true">
    <implementation class="org.eclipse.smila.http.server.util.RequestDispatcher" />
    <service>
       <provide interface="org.eclipse.smila.http.server.HttpHandler"/>
    </service>
    <reference
      name="RequestHandlers"
      interface="org.eclipse.smila.http.server.util.RequestHandler"
      bind="addRequestHandler"
      unbind="removeRequestHandler"
      cardinality="0..n"
      policy="dynamic"
      target="(rootContextPath=/dispatch)" 
      />    
</component>

This starts a dispatcher for the root context "/dispatch", i.e. it tries to handle all requests to http://host:port/dispatch... and uses all RequestHandler services that have a property rootContextPath set to "/dispatch". You can add this description to your own bundles to start several dispatchers for different root contexts, just be sure to import the bundles org.eclipse.smila.http.server and org.eclipse.smila.http.server.util even if they are not needed by the actual code.

We provide also a base class for RequestHandlers named org.eclipse.smila.http.server.util.ARequestHandler which implements the matches() method matching the URI part after the root context path using a regular expression string read from its own component properties specified in the DS file. E.g. in the test bundle we have OSGI-INF/requesthandler.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component name="org.eclipse.smila.http.server.test.MockRequestHandler" immediate="true">
    <implementation class="org.eclipse.smila.http.server.test.MockRequestHandler" />    
    <service>
       <provide interface="org.eclipse.smila.http.server.util.RequestHandler"/>
    </service>
    <property name="rootContextPath" type="String" value="/dispatch"/>
    <property name="uriPattern" type="String" value="/handle/([^/]+)/?$"/>    
</component>

This starts a simple RequestHandler service managed by the dispatcher described above and reacts on requests to http://host:port/dispatch/handle/<string-without-further-slashes>. Also, the base class provides methods to extract the parts of the request URI matched by the groups in the regular expression.

JSON Handlers

SMILA provides some base classes that make it quite easy to write handlers that receive requests and produce results using JSON. JSON is a very lightweight data format that makes interchange much more efficient than using XML. Moreover it is easy to interpret in Javascript based web user interfaces. Currently the following base classes are available in package org.eclipse.smila.http.server.json:

  • JsonHttpHandler: implements the HttpHandler interface
  • JsonRequestHandler: implements the RequestHandler interface and extends ARequestHandler
  • JsonBulkRequestHandler: implements the RequestHandler interface and extends ARequestHandler. Can take multiple JSON objects as input, but (normally) does not produce a result object. [Note: a result may be produced by the finish method and will be returned in the response as JSON.]

The first two handler expect a single record as input to be processed (of course, the record can be empty). The record is either parsed from a JSON request body (in POST or PUT requests) or constructed from the request parameters (in a GET request) and then a process() method implemented by the subclass is invoked with this record to do the actual processing. The result may be a single object, preferably a SMILA Record or Any object.

The latter handler expects a JSON "bulk" as its input. A bulk is a set of JSON Objects, each one printed on a single line (i.e. no newline or linefeed characters) and seperated by newline characters. The bulk is parsed and sent to a process() method implemented in a subclass one by one, so this handler makes it possible to push a lot of records into SMILA in a single request with high performance and low memory usage. After the complete bulk has been processed successfully, a finish() method implemented by the subclass is called to finalize the processing if necessary. A result produced by the finish method will be returned as a response.

The first handler is meant to be managed by the Http service immediately (register as HttpHandler) while the latter two are managed by a RequestDispatcher service (register them as RequestHandlers).

Other methods you may want to override in subclasses to customize the behavior depending on HTTP method used to invoke the handler, the actual request URI, input and output objects and exceptions throwm by the process method:

  • isValidMethod: Checks if the HTTP method is allowed for this handler. If not, a METHOD-NOT-ALLOWED status is returned to the client. By default only POST is allowed.
  • getSuccessStatus: HTTP status to return after successful processing. Default is ACCEPTED for the bulk handler and OK for the others.
  • writeResultObject: Add additional Object-to-JSON-serialization code if you do not return a SMILA Record or Any object or something else that can be serialized using a standard Jackson ObjectMapper. The latter usually works fine for Java objects like Collections or Maps or Beans.
  • getErrorStatus: HTTP status to return after failed processing, depending on the action exception. See the default implementation for the predefined mapping from exception type to status code.
Attachments

JsonHttpHandler and JsonRequestHandler support attachments for input records in POST requests, too. Such records must be sent as "multipart" requests, where the first part contains the record metadata as JSON, followed by binary parts.

For example, to submit a record with an attachment to a job (which is the main use case for sending attachments to SMILA), you can use the Apache HttpClient 4.x (not included with SMILA, however):

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
...
String jsonMetadata = ...;
byte[] attachment = ...;
HttpClient client = new DefaultHttpClient();
MultipartEntity multiPartMessage = new MultipartEntity();
StringBody jsonPart = new StringBody(jsonMetadata, 
                                     "application/json", 
                                     Charset.forName("UTF-8"));
multiPartMessage.addPart("metadata", jsonPart);
                      // the actual name of the metadata part is irrelevant
ByteArrayBody attachmentPart = new ByteArrayBody(attachment, 
                                                 "application/octet-stream", 
                                                 null); // no filename necessary
multiPartMessage.addPart("content", attachmentPart); 
                      // part name is the attachment name.
HttpPost request = new HttpPost("http://localhost:8080/smila/job/" + jobName + "/record");
request.setEntity(multiPartMessage);
HttpResponse response = client.execute(request);
...

The HTTP request than looks similar to this:

POST /smila/job/jobname/record
 
Content-Length: 12345
Content-Type: multipart/form-data; boundary=UMFOMEI8xxUic4zcDix9Utn4ORyVTcS3
 
--UMFOMEI8xxUic4zcDix9Utn4ORyVTcS3
Content-Disposition: form-data; name="metadata"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
 
{
	"_recordid": "...",
	// more attributes
}
--UMFOMEI8xxUic4zcDix9Utn4ORyVTcS3
Content-Disposition: form-data; name="content"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
 
... attachment content ...
 
--UMFOMEI8xxUic4zcDix9Utn4ORyVTcS3--

The HttpClient lib provides additional ContentBody implementations for adding parts from InputStreams or Files immediately instead of having to load them in memory first. Of course, you can also add multiple attachments to a single request, just add more parts with different attachment names.

We have also introduced a size limit for incoming records to protect SMILA against OutOfMemoryErrors by client requests. The size limit can be configured in configuration/org.eclipse.smila.http.server/httpserver.properties, the default value is 1 GiB:

# maximum size of request records (including attachments) at the ReST API 
# for JSON handlers that load the complete request data in memory (using JsonHttpUtils.convertRequest())
http.json.maxRequestRecordSize=1g

Values can use "k" (KiB), "m" (MiB), "g" (GiB) suffixes for better readability. The value describes the maximum size of the complete request, i.e. JSON metadata plus all attachments. Bigger requests are rejected with response code 400 BAD REQUEST. The limit does not concern handlers derived from JsonBulkRequestHandler or non-Json-handlers, as it should still be possible to implement "streaming handlers" that do not need to load the complete request in memory.

JettyHandler services

An OSGi service providing org.eclipse.smila.http.JettyHandler can be used to inject original Jetty handlers programmatically into the server. A JettyHandler must implement two methods:

  • org.eclipse.jetty.server.Handler getHandler(): the initialized Jetty handler. If it is not already a ContextHandler, it is wrapped inside one automatically by the HttpService.
  • String getRootContextPath(): the URI path prefix to be handled by this handler.

There is also a base class org.eclipse.smila.http.AJettyHandlerService available which reads the context path value from the DS component context and determines paths into the configuration area if the component context contains a bundle name for which access to the configuration space has been configured.

We have already created three (partly prototypical) implementations of this service. They provide only quite limited means of configuring the Jetty handler themselves, so they are provided rather as examples, meaning you can use them to base your own handlers on. If you require more configuration, you will either have to create your own variant of these services or use the jetty.xml to achieve the desired deployment/configuration.

The exemplary implementations (details follow below) are all contained in org.eclipse.smila.http.server.util.

ResourceHandlerService

Creates a Jetty ResourceHandler which can serve files from the configuration area. The test bundle shows how to use this in OSGI-INF/resourcehandler.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component name="org.eclipse.smila.http.server.util.ResourceHandlerService" immediate="true">
    <implementation class="org.eclipse.smila.http.server.util.ResourceHandlerService" />
    <service>
       <provide interface="org.eclipse.smila.http.server.JettyHandler"/>
    </service>
    <property name="rootContextPath" type="String" value="/resources"/>
    <property name="configBundle" type="String" value="org.eclipse.smila.http.server.test" />
    <property name="resourceBase" type="String" value="resources"/>    
    <property name="welcomeFiles" type="String" value="index.html"/>
</component>

This creates a ResourceHandler which can provide files from configuration/org.eclipse.smila.http.server.test/resources at the URL http://.../resources/..., and returns "index.html" for requests containing only a directory name and no explicit filename in the URL.

ServletContextService

Creates and registers a ServletContextHandler with a single javax.servlet instance already created (so it may be helpful in case of classloading problems, because the servlet instance can be created inside its own bundle context). Again, you can find a simple example in the test bundle at OSGI-INF/servlethandler.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component name="org.eclipse.smila.http.server.util.ServletContextService" immediate="true">
    <implementation class="org.eclipse.smila.http.server.util.ServletContextService" />
    <service>
       <provide interface="org.eclipse.smila.http.server.JettyHandler"/>
    </service>
    <property name="rootContextPath" type="String" value="/servlet"/>
    <property name="servletClassName" type="String" value="org.eclipse.smila.http.server.test.MockServlet" />
    <property name="servletPath" type="String" value="/mock"/>    
</component>

This registers an instance of the named class as a servlet which can be invoked using http://.../servlet/mock.

WebappContextService

Creates a handler for a web application. The example from the test bundle is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<component name="org.eclipse.smila.http.server.util.WebappContextService" immediate="true">
    <implementation class="org.eclipse.smila.http.server.util.WebappContextService" />
    <service>
       <provide interface="org.eclipse.smila.http.server.JettyHandler"/>
    </service>
    <property name="rootContextPath" type="String" value="/webapp"/>
    <property name="configBundle" type="String" value="org.eclipse.smila.http.server.test" />
    <property name="webappDir" type="String" value="webapp"/>    
</component>

This makes the web application available as http://.../webapp in the directory webapp of the configuration area of bundle org.eclipse.smila.http.server.test. The web application must not be assembled into a WAR file. The descriptor file must be located at WEB-INF/web.xml and the configuration directory for the named bundle must also provide a file named webdefault.xml containing default values for the web.xml. An override-web.xml is not supported. See configuration/org.eclipse.smila.http.server.test/ for an exemplary simple setup.