nifi-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From marka...@apache.org
Subject [2/2] incubator-nifi git commit: NIFI-221: Implemented HandleHttpRequest, HandleHttpResponse, and associated controller services
Date Thu, 15 Jan 2015 01:37:06 GMT
NIFI-221: Implemented HandleHttpRequest, HandleHttpResponse, and associated controller services


Project: http://git-wip-us.apache.org/repos/asf/incubator-nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-nifi/commit/d377a97a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-nifi/tree/d377a97a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-nifi/diff/d377a97a

Branch: refs/heads/NIFI221
Commit: d377a97ab19d2a59c3fa647b47515db1020a2d87
Parents: 01a63bd
Author: Mark Payne <markap14@hotmail.com>
Authored: Wed Jan 14 20:36:49 2015 -0500
Committer: Mark Payne <markap14@hotmail.com>
Committed: Wed Jan 14 20:36:49 2015 -0500

----------------------------------------------------------------------
 assembly/pom.xml                                |   5 +
 .../standard-bundle/standard-processors/pom.xml |   4 +
 .../processors/standard/HandleHttpRequest.java  | 514 +++++++++++++++++++
 .../processors/standard/HandleHttpResponse.java | 171 ++++++
 .../org.apache.nifi.processor.Processor         |   2 +
 .../index.html                                  | 255 +++++++++
 .../index.html                                  | 112 ++++
 .../http-context-map-api/pom.xml                |  25 +
 .../org/apache/nifi/http/HttpContextMap.java    |  71 +++
 .../TooManyOutstandingRequestsException.java    |  26 +
 .../http-context-map-nar/pom.xml                |  39 ++
 .../http-context-map/bin/pom.xml                |  42 ++
 .../http/StandardHttpContextMap$Wrapper.class   | Bin 0 -> 3833 bytes
 .../nifi/http/StandardHttpContextMap.class      | Bin 0 -> 5002 bytes
 ...org.apache.nifi.controller.ControllerService |   1 +
 .../index.html                                  |  64 +++
 .../http-context-map/pom.xml                    |  46 ++
 .../nifi/http/StandardHttpContextMap.java       | 112 ++++
 ...org.apache.nifi.controller.ControllerService |   1 +
 .../index.html                                  |  67 +++
 .../http-context-map-bundle/pom.xml             |  34 ++
 nar-bundles/standard-services/pom.xml           |   2 +
 .../standard-services-api-nar/pom.xml           |   5 +
 pom.xml                                         |   8 +-
 24 files changed, 1605 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/assembly/pom.xml
----------------------------------------------------------------------
diff --git a/assembly/pom.xml b/assembly/pom.xml
index e785c45..fd3f623 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -166,6 +166,11 @@
             <artifactId>kafka-nar</artifactId>
             <type>nar</type>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>http-context-map-nar</artifactId>
+            <type>nar</type>
+        </dependency>
     </dependencies>
     
     <properties>        

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-bundle/standard-processors/pom.xml
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-bundle/standard-processors/pom.xml b/nar-bundles/standard-bundle/standard-processors/pom.xml
index f4cd2a0..d9cfc9e 100644
--- a/nar-bundles/standard-bundle/standard-processors/pom.xml
+++ b/nar-bundles/standard-bundle/standard-processors/pom.xml
@@ -138,6 +138,10 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
+            <artifactId>http-context-map-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
             <artifactId>distributed-cache-client-service</artifactId>
             <scope>test</scope>
         </dependency>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java b/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java
new file mode 100644
index 0000000..3ec9a84
--- /dev/null
+++ b/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java
@@ -0,0 +1,514 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.nifi.processors.standard;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+import javax.security.cert.X509Certificate;
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.http.HttpContextMap;
+import org.apache.nifi.http.TooManyOutstandingRequestsException;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.annotation.CapabilityDescription;
+import org.apache.nifi.processor.annotation.OnScheduled;
+import org.apache.nifi.processor.annotation.OnStopped;
+import org.apache.nifi.processor.annotation.Tags;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.ssl.SSLContextService;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import com.sun.jersey.api.client.ClientResponse.Status;
+
+@Tags({"http", "https", "request", "listen", "ingress", "web service"})
+@CapabilityDescription("Starts an HTTP Server and listens for HTTP Requests. For each request, creates a FlowFile and transfers to 'success'. This Processor is designed to be used in conjunction with the HandleHttpResponse Processor in order to create a Web Service")
+public class HandleHttpRequest extends AbstractProcessor {
+    public static final String HTTP_CONTEXT_ID = "http.context.identifier";
+    
+    // Allowable values for client auth
+    public static final AllowableValue CLIENT_NONE = new AllowableValue("No Authentication", "Processor will not authenticate clients. Anyone can communicate with this Processor anonymously");
+    public static final AllowableValue CLIENT_WANT = new AllowableValue("Want Authentication", "Processor will try to verify the client but if unable to verify will allow the client to communicate anonymously");
+    public static final AllowableValue CLIENT_NEED = new AllowableValue("Need Authentication", "Processor will reject communications from any client unless the client provides a certificate that is trusted by the TrustStore specified in the SSL Context Service");
+    
+    
+    public static final PropertyDescriptor PORT = new PropertyDescriptor.Builder()
+        .name("Listening Port")
+        .description("The Port to listen on for incoming HTTP requests")
+        .required(true)
+        .addValidator(StandardValidators.createLongValidator(0L, 65535L, true))
+        .expressionLanguageSupported(false)
+        .defaultValue("80")
+        .build();
+    public static final PropertyDescriptor HOSTNAME = new PropertyDescriptor.Builder()
+        .name("Hostname")
+        .description("The Hostname to bind to. If not specified, will bind to all hosts")
+        .required(false)
+        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+        .expressionLanguageSupported(false)
+        .build();
+    public static final PropertyDescriptor HTTP_CONTEXT_MAP = new PropertyDescriptor.Builder()
+        .name("HTTP Context Map")
+        .description("The HTTP Context Map Controller Service to use for caching the HTTP Request Information")
+        .required(true)
+        .identifiesControllerService(HttpContextMap.class)
+        .build();
+    public static final PropertyDescriptor SSL_CONTEXT = new PropertyDescriptor.Builder()
+        .name("SSL Context Service")
+        .description("The SSL Context Service to use in order to secure the server. If specified, the server will accept only HTTPS requests; otherwise, the server will accept only HTTP requests")
+        .required(false)
+        .identifiesControllerService(SSLContextService.class)
+        .build();
+    public static final PropertyDescriptor PATH_REGEX = new PropertyDescriptor.Builder()
+        .name("Allowed Paths")
+        .description("A Regular Expression that specifies the valid HTTP Paths that are allowed in the incoming URL Requests. If this value is specified and the path of the HTTP Requests does not match this Regular Expression, the Processor will respond with a 404: NotFound")
+        .required(false)
+        .addValidator(StandardValidators.REGULAR_EXPRESSION_VALIDATOR)
+        .expressionLanguageSupported(false)
+        .build();
+    public static final PropertyDescriptor ALLOW_GET = new PropertyDescriptor.Builder()
+        .name("Allow GET")
+        .description("Allow HTTP GET Method")
+        .required(true)
+        .allowableValues("true", "false")
+        .defaultValue("true")
+        .build();
+    public static final PropertyDescriptor ALLOW_POST = new PropertyDescriptor.Builder()
+        .name("Allow POST")
+        .description("Allow HTTP POST Method")
+        .required(true)
+        .allowableValues("true", "false")
+        .defaultValue("true")
+        .build();
+    public static final PropertyDescriptor ALLOW_PUT = new PropertyDescriptor.Builder()
+        .name("Allow PUT")
+        .description("Allow HTTP PUT Method")
+        .required(true)
+        .allowableValues("true", "false")
+        .defaultValue("true")
+        .build();
+    public static final PropertyDescriptor ALLOW_DELETE = new PropertyDescriptor.Builder()
+        .name("Allow DELETE")
+        .description("Allow HTTP DELETE Method")
+        .required(true)
+        .allowableValues("true", "false")
+        .defaultValue("true")
+        .build();
+    public static final PropertyDescriptor ALLOW_HEAD = new PropertyDescriptor.Builder()
+        .name("Allow HEAD")
+        .description("Allow HTTP HEAD Method")
+        .required(true)
+        .allowableValues("true", "false")
+        .defaultValue("false")
+        .build();
+    public static final PropertyDescriptor ALLOW_OPTIONS = new PropertyDescriptor.Builder()
+        .name("Allow OPTIONS")
+        .description("Allow HTTP OPTIONS Method")
+        .required(true)
+        .allowableValues("true", "false")
+        .defaultValue("false")
+        .build();
+    public static final PropertyDescriptor ADDITIONAL_METHODS = new PropertyDescriptor.Builder()
+        .name("Additional HTTP Methods")
+        .description("A comma-separated list of non-standard HTTP Methods that should be allowed")
+        .required(false)
+        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+        .expressionLanguageSupported(false)
+        .build();
+    public static final PropertyDescriptor CLIENT_AUTH = new PropertyDescriptor.Builder()
+        .name("Client Authentication")
+        .description("Specifies whether or not the Processor should authenticate clients. This value is ignored if the <SSL Context Service> Property is not specified or the SSL Context provided uses only a KeyStore and not a TrustStore.")
+        .required(true)
+        .allowableValues(CLIENT_NONE, CLIENT_WANT, CLIENT_NEED)
+        .defaultValue(CLIENT_NONE.getValue())
+        .build();
+    
+    
+    public static final Relationship REL_SUCCESS = new Relationship.Builder()
+        .name("success")
+        .description("All content that is received is routed to the 'success' relationship")
+        .build();
+    
+    private volatile Server server;
+    private final BlockingQueue<HttpRequestContainer> containerQueue = new LinkedBlockingQueue<>(50);
+    
+    
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        final List<PropertyDescriptor> descriptors = new ArrayList<>();
+        descriptors.add(PORT);
+        descriptors.add(HOSTNAME);
+        descriptors.add(SSL_CONTEXT);
+        descriptors.add(HTTP_CONTEXT_MAP);
+        descriptors.add(PATH_REGEX);
+        descriptors.add(ALLOW_GET);
+        descriptors.add(ALLOW_POST);
+        descriptors.add(ALLOW_PUT);
+        descriptors.add(ALLOW_DELETE);
+        descriptors.add(ALLOW_HEAD);
+        descriptors.add(ALLOW_OPTIONS);
+        descriptors.add(ADDITIONAL_METHODS);
+
+        return descriptors;
+    }
+    
+    @Override
+    public Set<Relationship> getRelationships() {
+        return Collections.singleton(REL_SUCCESS);
+    }
+    
+    
+    @OnScheduled
+    public void initializeServer(final ProcessContext context) throws Exception {
+        final String host = context.getProperty(HOSTNAME).getValue();
+        final int port = context.getProperty(PORT).asInteger();
+        final SSLContextService sslService = context.getProperty(SSL_CONTEXT).asControllerService(SSLContextService.class);
+        
+        final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
+        final boolean need;
+        final boolean want;
+        if ( CLIENT_NEED.equals(clientAuthValue) ) {
+            need = true;
+            want = false;
+        } else if ( CLIENT_WANT.equals(clientAuthValue) ) {
+            need = false;
+            want = true;
+        } else {
+            need = false;
+            want = false;
+        }
+        
+        final SslContextFactory sslFactory = (sslService == null) ? null : createSslFactory(sslService, need, want);
+        final Server server = new Server(port);
+        
+        // create the http configuration
+        final HttpConfiguration httpConfiguration = new HttpConfiguration();
+        if ( sslFactory == null ) {
+            // create the connector
+            final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
+
+            // set host and port
+            if (StringUtils.isNotBlank(host)) {
+                http.setHost(host);
+            }
+            http.setPort(port);
+
+            // add this connector
+            server.setConnectors(new Connector[] {http});
+        } else {
+            // add some secure config
+            final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
+            httpsConfiguration.setSecureScheme("https");
+            httpsConfiguration.setSecurePort(port);
+            httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
+
+            // build the connector
+            final ServerConnector https = new ServerConnector(server,
+                    new SslConnectionFactory(sslFactory, "http/1.1"),
+                    new HttpConnectionFactory(httpsConfiguration));
+
+            // set host and port
+            if (StringUtils.isNotBlank(host)) {
+                https.setHost(host);
+            }
+            https.setPort(port);
+
+            // add this connector
+            server.setConnectors(new Connector[] {https});
+        }
+        
+        final Set<String> allowedMethods = new HashSet<>();
+        if ( context.getProperty(ALLOW_GET).asBoolean() ) {
+            allowedMethods.add("GET");
+        }
+        if ( context.getProperty(ALLOW_POST).asBoolean() ) {
+            allowedMethods.add("POST");
+        }
+        if ( context.getProperty(ALLOW_PUT).asBoolean() ) {
+            allowedMethods.add("PUT");
+        }
+        if ( context.getProperty(ALLOW_DELETE).asBoolean() ) {
+            allowedMethods.add("DELETE");
+        }
+        if ( context.getProperty(ALLOW_HEAD).asBoolean() ) {
+            allowedMethods.add("HEAD");
+        }
+        if ( context.getProperty(ALLOW_OPTIONS).asBoolean() ) {
+            allowedMethods.add("OPTIONS");
+        }
+        
+        final String additionalMethods = context.getProperty(ADDITIONAL_METHODS).getValue();
+        if ( additionalMethods != null ) {
+            for ( final String additionalMethod : additionalMethods.split(",") ) {
+                final String trimmed = additionalMethod.trim();
+                if ( !trimmed.isEmpty() ) {
+                    allowedMethods.add(trimmed.toUpperCase());
+                }
+            }
+        }
+        
+        final String pathRegex = context.getProperty(PATH_REGEX).getValue();
+        final Pattern pathPattern = (pathRegex == null) ? null : Pattern.compile(pathRegex);
+        
+        server.setHandler(new AbstractHandler() {
+            @Override
+            public void handle(final String target, final Request baseRequest, final HttpServletRequest request, 
+                        final HttpServletResponse response) throws IOException, ServletException {
+                
+                final String requestUri = request.getRequestURI();
+                if ( !allowedMethods.contains(request.getMethod().toUpperCase()) ) {
+                    getLogger().info("Sending back METHOD_NOT_ALLOWED response to {}; method was {}; request URI was {}", 
+                            new Object[] {request.getRemoteAddr(), request.getMethod(), requestUri});
+                    response.sendError(Status.METHOD_NOT_ALLOWED.getStatusCode());
+                    return;
+                }
+                
+                if ( pathPattern != null ) {
+                    final URI uri;
+                    try {
+                        uri = new URI(requestUri);
+                    } catch (final URISyntaxException e) {
+                        throw new ServletException(e);
+                    }
+                    
+                    if ( !pathPattern.matcher(uri.getPath()).matches() ) {
+                        response.sendError(Status.NOT_FOUND.getStatusCode());
+                        getLogger().info("Sending back NOT_FOUND response to {}; request was {} {}", 
+                                new Object[] {request.getRemoteAddr(), request.getMethod(), requestUri});
+                        return;
+                    }
+                }
+                
+                // TODO: If destination queues full, should send back a 503: Service Unavailable.
+                // Right now, that information, though, is only in the ProcessSession, not the ProcessContext,
+                // so it is not known to us. Should see if it can be added to the ProcessContext.
+                final AsyncContext async = baseRequest.startAsync();
+                final boolean added = containerQueue.offer(new HttpRequestContainer(request, response, async));
+                
+                if ( added ) {
+                    getLogger().debug("Added Http Request to queue for {} {} from {}", new Object[] {request.getMethod(), requestUri, request.getRemoteAddr()});
+                } else {
+                    getLogger().info("Sending back a SERVICE_UNAVAILABLE response to {}; request was {} {}", 
+                            new Object[] {request.getRemoteAddr(), request.getMethod(), request.getRemoteAddr()});
+
+                    response.sendError(Status.SERVICE_UNAVAILABLE.getStatusCode());
+                    response.flushBuffer();
+                    async.complete();
+                }
+            }
+        });
+        
+        this.server = server;
+        server.start();
+        
+        getLogger().info("Server started and listening on port " + getPort());
+    }
+    
+    protected int getPort() {
+        for ( final Connector connector : server.getConnectors() ) {
+            if ( connector instanceof ServerConnector ) {
+                return ((ServerConnector) connector).getPort();
+            }
+        }
+        
+        throw new IllegalStateException("Server is not listening on any ports");
+    }
+    
+    protected int getRequestQueueSize() {
+        return containerQueue.size();
+    }
+    
+    private SslContextFactory createSslFactory(final SSLContextService sslService, final boolean needClientAuth, final boolean wantClientAuth) {
+        final SslContextFactory sslFactory = new SslContextFactory();
+        
+        sslFactory.setNeedClientAuth(needClientAuth);
+        sslFactory.setWantClientAuth(wantClientAuth);
+        
+        if ( sslService.isKeyStoreConfigured() ) {
+            sslFactory.setKeyStorePath(sslService.getKeyStoreFile());
+            sslFactory.setKeyStorePassword(sslService.getKeyStorePassword());
+            sslFactory.setKeyStoreType(sslService.getKeyStoreType());
+        }
+
+        if ( sslService.isTrustStoreConfigured() ) {
+            sslFactory.setTrustStorePath(sslService.getTrustStoreFile());
+            sslFactory.setTrustStorePassword(sslService.getTrustStorePassword());
+            sslFactory.setTrustStoreType(sslService.getTrustStoreType());
+        }
+        
+        return sslFactory;
+    }
+    
+    @OnStopped
+    public void shutdown() throws Exception {
+        if ( server != null ) {
+            getLogger().debug("Shutting down server");
+            server.stop();
+            server.join();
+            getLogger().info("Shut down {}", new Object[] {server});
+        }
+    }
+    
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
+        final HttpRequestContainer container = containerQueue.poll();
+        if ( container == null ) {
+            return;
+        }
+
+        final long start = System.nanoTime();
+        final HttpServletRequest request = container.getRequest();
+        FlowFile flowFile = session.create();
+        try {
+            flowFile = session.importFrom(request.getInputStream(), flowFile);
+        } catch (final IOException e) {
+            getLogger().error("Failed to receive content from HTTP Request from {} due to {}", new Object[] {request.getRemoteAddr(), e});
+            session.remove(flowFile);
+            return;
+        }
+        
+        final String contextIdentifier = UUID.randomUUID().toString();
+        final Map<String, String> attributes = new HashMap<>();
+        putAttribute(attributes, HTTP_CONTEXT_ID, contextIdentifier);
+        putAttribute(attributes, "mime.type", request.getContentType());
+        putAttribute(attributes, "http.servlet.path", request.getServletPath());
+        putAttribute(attributes, "http.context.path", request.getContextPath());
+        putAttribute(attributes, "http.method", request.getMethod());
+        putAttribute(attributes, "http.query.string", request.getQueryString());
+        putAttribute(attributes, "http.remote.host", request.getRemoteHost());
+        putAttribute(attributes, "http.remote.addr", request.getRemoteAddr());
+        putAttribute(attributes, "http.remote.user", request.getRemoteUser());
+        putAttribute(attributes, "http.request.uri", request.getRequestURI());
+        putAttribute(attributes, "http.auth.type", request.getAuthType());
+        
+        final Enumeration<String> headerNames = request.getHeaderNames();
+        while ( headerNames.hasMoreElements() ) {
+            final String headerName = headerNames.nextElement();
+            final String headerValue = request.getHeader(headerName);
+            putAttribute(attributes, "http.headers." + headerName, headerValue);
+        }
+        
+        final Principal principal = request.getUserPrincipal();
+        if ( principal != null ) {
+            putAttribute(attributes, "http.principal.name", principal.getName());
+        }
+        
+        final X509Certificate certs[] = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+        if ( certs != null && certs.length > 0 ) {
+            final X509Certificate cert = certs[0];
+            final String subjectDn = cert.getSubjectDN().getName();
+            final String issuerDn = cert.getIssuerDN().getName();
+            
+            putAttribute(attributes, "http.subject.dn", subjectDn);
+            putAttribute(attributes, "http.issuer.dn", issuerDn);
+        }
+        
+        flowFile = session.putAllAttributes(flowFile, attributes);
+        
+        final HttpContextMap contextMap = context.getProperty(HTTP_CONTEXT_MAP).asControllerService(HttpContextMap.class);
+        try {
+            contextMap.register(contextIdentifier, request, container.getResponse(), container.getContext());
+        } catch (final TooManyOutstandingRequestsException tmore) {
+            getLogger().warn("Received request from {} but could not process it because too many requests are already outstanding; responding with SERVICE_UNAVAILABLE", new Object[] {request.getRemoteAddr()});
+            
+            try {
+                container.getResponse().setStatus(Status.SERVICE_UNAVAILABLE.getStatusCode());
+                container.getResponse().flushBuffer();
+                container.getContext().complete();
+            } catch (final Exception e) {
+                getLogger().warn("Failed to respond with SERVICE_UNAVAILABLE message to {} due to {}", new Object[] {request.getRemoteAddr(), e});
+            }
+            
+            return;
+        }
+        
+        final long receiveMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
+        session.getProvenanceReporter().receive(flowFile, request.getRequestURI(), receiveMillis);
+        session.transfer(flowFile, REL_SUCCESS);
+        getLogger().info("Transferring {} to 'success'; received from {}", new Object[] {flowFile, request.getRemoteAddr()});
+        
+        
+    }
+    
+    private void putAttribute(final Map<String, String> map, final String key, final String value) {
+        if ( value == null ) {
+            return;
+        }
+        
+        map.put(key, value);
+    }
+    
+    
+    private static class HttpRequestContainer {
+        private final HttpServletRequest request;
+        private final HttpServletResponse response;
+        private final AsyncContext context;
+        
+        public HttpRequestContainer(final HttpServletRequest request, final HttpServletResponse response, final AsyncContext async) {
+            this.request = request;
+            this.response = response;
+            this.context = async;
+        }
+
+        public HttpServletRequest getRequest() {
+            return request;
+        }
+
+        public HttpServletResponse getResponse() {
+            return response;
+        }
+
+        public AsyncContext getContext() {
+            return context;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java b/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java
new file mode 100644
index 0000000..0f4abf2
--- /dev/null
+++ b/nar-bundles/standard-bundle/standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpResponse.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.nifi.processors.standard;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.http.HttpContextMap;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.annotation.CapabilityDescription;
+import org.apache.nifi.processor.annotation.Tags;
+import org.apache.nifi.processor.exception.ProcessException;
+import org.apache.nifi.processor.util.StandardValidators;
+
+@Tags({"http", "https", "response", "egress", "web service"})
+@CapabilityDescription("Sends an HTTP Response to the Requestor that generated a FlowFile. This Processor is designed to be used in conjunction with the HandleHttpRequest in order to create a web service.")
+public class HandleHttpResponse extends AbstractProcessor {
+    public static final Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");
+    public static final String HTTP_CONTEXT_ID = "http.context.identifier";
+
+    public static final PropertyDescriptor STATUS_CODE = new PropertyDescriptor.Builder()
+        .name("HTTP Status Code")
+        .description("The HTTP Status Code to use when responding to the HTTP Request. See Section 10 of RFC 2616 for more information.")
+        .required(true)
+        .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+        .expressionLanguageSupported(true)
+        .build();
+    public static final PropertyDescriptor HTTP_CONTEXT_MAP = new PropertyDescriptor.Builder()
+        .name("HTTP Context Map")
+        .description("The HTTP Context Map Controller Service to use for caching the HTTP Request Information")
+        .required(true)
+        .identifiesControllerService(HttpContextMap.class)
+        .build();
+    
+    public static final Relationship REL_SUCCESS = new Relationship.Builder()
+        .name("success")
+        .description("FlowFiles will be routed to this Relationship after the response has been successfully sent to the requestor")
+        .build();
+    public static final Relationship REL_FAILURE = new Relationship.Builder()
+        .name("failure")
+        .description("FlowFiles will be routed to this Relationship if the Processor is unable to respond to the requestor. This may happen, for instance, if the connection times out or if NiFi is restarted before responding to the HTTP Request.")
+        .build();
+
+    
+    @Override
+    public final List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        final List<PropertyDescriptor> properties = new ArrayList<>();
+        properties.add(STATUS_CODE);
+        properties.add(HTTP_CONTEXT_MAP);
+        return properties;
+    }
+    
+    @Override
+    public Set<Relationship> getRelationships() {
+        final Set<Relationship> relationships = new HashSet<>();
+        relationships.add(REL_SUCCESS);
+        relationships.add(REL_FAILURE);
+        return relationships;
+    }
+    
+    @Override
+    protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
+        return new PropertyDescriptor.Builder()
+            .description("Specifies the value to send for the '" + propertyDescriptorName + "' HTTP Header")
+            .name(propertyDescriptorName)
+            .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .dynamic(true)
+            .expressionLanguageSupported(true)
+            .build();
+    }
+    
+    
+    @Override
+    public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
+        FlowFile flowFile = session.get();
+        if ( flowFile == null ) {
+            return;
+        }
+        
+        final String contextIdentifier = flowFile.getAttribute(HTTP_CONTEXT_ID);
+        if ( contextIdentifier == null ) {
+            session.transfer(flowFile, REL_FAILURE);
+            getLogger().warn("Failed to respond to HTTP request for {} because FlowFile did not have an "
+                    + "'http.context.identifier' attribute", new Object[] {flowFile});
+            return;
+        }
+        
+        final String statusCodeValue = context.getProperty(STATUS_CODE).evaluateAttributeExpressions(flowFile).getValue();
+        if ( !isNumber(statusCodeValue) ) {
+            session.transfer(flowFile, REL_FAILURE);
+            getLogger().error("Failed to response to HTTP request for {} because status code was '{}', which is not a valid number", new Object[] {flowFile, statusCodeValue});
+        }
+        
+        final HttpContextMap contextMap = context.getProperty(HTTP_CONTEXT_MAP).asControllerService(HttpContextMap.class);
+        final HttpServletResponse response = contextMap.getResponse(contextIdentifier);
+        if ( response == null ) {
+            session.transfer(flowFile, REL_FAILURE);
+            getLogger().error("Failed to respond to HTTP request for {} because FlowFile had an '" + HTTP_CONTEXT_ID + "' "
+                    + "attribute of {} but could not find an HTTP Response Object for this identifier", new Object[] {flowFile});
+            return;
+        }
+        
+        final int statusCode = Integer.parseInt(statusCodeValue);
+        response.setStatus(statusCode);
+        
+        for ( final Map.Entry<PropertyDescriptor, String> entry : context.getProperties().entrySet() ) {
+            final PropertyDescriptor descriptor = entry.getKey();
+            if ( descriptor.isDynamic() ) {
+                final String headerName = descriptor.getName();
+                final String headerValue = context.getProperty(descriptor).evaluateAttributeExpressions(flowFile).getValue();
+                
+                if ( !headerValue.trim().isEmpty() ) {
+                    response.setHeader(headerName, headerValue);
+                }
+            }
+        }
+        
+        try {
+            session.exportTo(flowFile, response.getOutputStream());
+            response.flushBuffer();
+        } catch (final IOException ioe) {
+            session.transfer(flowFile, REL_FAILURE);
+            getLogger().error("Failed to respond to HTTP request for {} due to {}", new Object[] {flowFile, ioe});
+            return;
+        }
+        
+        contextMap.complete(contextIdentifier);
+        session.transfer(flowFile, REL_SUCCESS);
+        getLogger().info("Successfully responded to HTTP Request for {} with status code {}", new Object[] {flowFile, statusCode});
+    }
+
+    private static boolean isNumber(final String value) {
+        if ( value.length() == 0 ) {
+            return false;
+        }
+        
+        for (int i=0; i < value.length(); i++) {
+            if ( !Character.isDigit(value.charAt(i)) ) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-bundle/standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-bundle/standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nar-bundles/standard-bundle/standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index 5f86b11..67a8e80 100644
--- a/nar-bundles/standard-bundle/standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nar-bundles/standard-bundle/standard-processors/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -28,6 +28,8 @@ org.apache.nifi.processors.standard.GetFile
 org.apache.nifi.processors.standard.GetFTP
 org.apache.nifi.processors.standard.GetHTTP
 org.apache.nifi.processors.standard.GetSFTP
+org.apache.nifi.processors.standard.HandleHttpRequest
+org.apache.nifi.processors.standard.HandleHttpResponse
 org.apache.nifi.processors.standard.HashAttribute
 org.apache.nifi.processors.standard.HashContent
 org.apache.nifi.processors.standard.IdentifyMimeType

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpRequest/index.html
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpRequest/index.html b/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpRequest/index.html
new file mode 100644
index 0000000..d3da666
--- /dev/null
+++ b/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpRequest/index.html
@@ -0,0 +1,255 @@
+<!DOCTYPE html>
+<html lang="en">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <head>
+        <meta charset="utf-8" />
+        <title>HandleHttpRequest</title>
+        <link rel="stylesheet" href="../../css/component-usage.css" type="text/css" />
+    </head>
+
+    <body>
+        <!-- Processor Documentation ================================================== -->
+        <h2>Description:</h2>
+        <p>
+            This processor starts an HTTP server and creates a FlowFile for each HTTP Request that it receives. The Processor leaves
+            the HTTP Connection open and is intended to be used in conjunction with a 
+            <a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a> Processor.
+        </p>
+        
+        <p>
+        	The pairing of this Processor with a <a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a> Processor
+        	provides the ability to use NiFi to visually construct a web server that can carry out any functionality that is available
+        	through the existing Processors. For example, one could construct a Web-based front end to an SFTP Server by constructing a
+        	flow such as:
+        </p>
+        
+        <p>
+        	<a href="index.html">HandleHttpRequest</a> -> 
+        	<a href="../org.apache.nifi.processors.standard.PutSFTP/index.html">PutSFTP</a> ->
+			<a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a>
+        </p>
+        
+        <p>
+        	The HandleHttpRequest Processor provides several Properties to configure which methods are supported, the paths that are
+        	supported, and SSL configuration. The FlowFiles that are generated by this Processor have the following attributes added to
+        	them, providing powerful routing capabilities and traceability of all data:
+        </p>
+        
+        <table>
+        	<thead>
+        		<th>Attribute Name</th>
+        		<th>Attribute Description</th>
+        	</thead>
+        	<tbody>
+        		<tr>
+        			<td>http.context.identifier</td>
+        			<td>An identifier that allows the HandleHttpRequest and HandleHttpResponse to coordinate which FlowFile belongs
+        				to which HTTP Request/Response.</td>
+        		</tr>
+        		<tr>
+        			<td>mime.type</td>
+        			<td>The MIME Type of the data, according to the HTTP Header "Content-Type"</td>
+        		</tr>
+        		<tr>
+        			<td>http.servlet.path</td>
+        			<td>The part of the request URL that is considered the Servlet Path</td>
+        		</tr>
+        		<tr>
+        			<td>http.context.path</td>
+        			<td>The part of the request URL that is considered to be the Context Path</td>
+        		</tr>
+        		<tr>
+        			<td>http.method</td>
+        			<td>The HTTP Method that was used for the request, such as GET or POST</td>
+        		</tr>
+        		<tr>
+        			<td>http.query.string</td>
+        			<td>The query string portion of hte Request URL</td>
+        		</tr>
+        		<tr>
+        			<td>http.remote.host</td>
+        			<td>The hostname of the requestor</td>
+        		</tr>
+        		<tr>
+        			<td>http.remote.addr</td>
+        			<td>The hostname:port combination of the requestor</td>
+        		</tr>
+        		<tr>
+        			<td>http.remote.user</td>
+        			<td>The username of the requestor</td>
+        		</tr>
+        		<tr>
+        			<td>http.request.uri</td>
+        			<td>The full Request URL</td>
+        		</tr>
+        		<tr>
+        			<td>http.auth.type</td>
+        			<td>The type of HTTP Authorization used</td>
+        		</tr>
+        		<tr>
+        			<td>http.principal.name</td>
+        			<td>The name of the authenticated user making the request</td>
+        		</tr>
+        		<tr>
+        			<td>http.subject.dn</td>
+        			<td>The Distinguished Name of the requestor. This value will not be populated unless the Processor is
+        				configured to use an SSLContext Service</td>
+        		</tr>
+        		<tr>
+        			<td>http.issuer.dn</td>
+        			<td>The Distinguished Name of the entity that issued the Subject's certificate. This value will not be 
+        				populated unless the Processor is configured to use an SSLContext Service</td>
+        		</tr>
+        		<tr>
+        			<td>http.headers.XXX</td>
+        			<td>Each of the HTTP Headers that is received in the request will be added as an attribute, prefixed
+        				with "http.headers." For example, if the request contains an HTTP Header named "x-my-header",
+        				then the value will be added to an attribute named "http.headers.x-my-header"</td>
+        		</tr>
+        	</tbody>
+        </table>
+        
+        
+        <p>
+            <strong>Properties:</strong>
+        </p>
+        <p>
+            In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. If a property has a default value, it is indicated. If a property supports the use of the NiFi Expression Language (or simply, "expression language"), that is also indicated.
+        </p>
+        <ul>
+            <li><strong>Listening Port</strong>
+                <ul>
+                    <li>The port to listen on for incoming HTTP Requests</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li>Hostname
+                <ul>
+                    <li>The Hostname to bind to. If not specified, will bind to all hosts</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>HTTP Context Map</strong>
+                <ul>
+                    <li>The HTTP Context Map Controller Service to use for caching the HTTP Request Information</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li>SSL Context Service
+                <ul>
+                    <li>The Controller Service to use for obtaining an SSL Context. The SSL Context controller service is a mechanism for providing all the	security properties that allow for secure communications between NiFi extensions and other systems. See the User Guide or the Controller Services documentation via the "help" link in the upper-right corner of the GUI for more information about the StandardSSLContextService. The value for this property is the identifier name that is configured in the StandardSSLContextService.</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            
+            <li><strong>Allowed Paths</strong>
+                <ul>
+                    <li>A Regular Expression that specifies the valid HTTP Paths that are allowed in the incoming URL Requests. If this value is specified and the path of the HTTP Requests does not match this Regular Expression, the Processor will respond with a 404: NotFound</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Allow GET</strong>
+                <ul>
+                    <li>Specifies whether or not to allow HTTP GET Method</li>
+                    <li>Default value: true</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Allow POST</strong>
+                <ul>
+                    <li>Specifies whether or not to allow HTTP POST Method</li>
+                    <li>Default value: true</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Allow PUT</strong>
+                <ul>
+                    <li>Specifies whether or not to allow HTTP PUT Method</li>
+                    <li>Default value: true</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Allow DELETE</strong>
+                <ul>
+                    <li>Specifies whether or not to allow HTTP DELETE Method</li>
+                    <li>Default value: true</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Allow HEAD</strong>
+                <ul>
+                    <li>Specifies whether or not to allow HTTP HEAD Method</li>
+                    <li>Default value: false</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Allow OPTIONS</strong>
+                <ul>
+                    <li>Specifies whether or not to allow HTTP OPTIONS Method</li>
+                    <li>Default value: false</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li>Additional HTTP Methods
+                <ul>
+                    <li>A comma-separated list of non-standard HTTP Methods that should be allowed</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+            <li><strong>Client Authentication</strong>
+                <ul>
+                    <li>Specifies whether or not the Processor should authenticate clients. This value is ignored if the &lt;SSL Context Service&gt; Property is not specified or the SSL Context provided uses only a KeyStore and not a TrustStore.</li>
+                    <li>
+                    	The following values are allowed:
+                    	<ul>
+                    		<li><b>No Authentication</b> - Processor will not authenticate clients. Anyone can communicate with this Processor anonymously</li>
+                    		<li><b>Want Authentication</b> - Processor will try to verify the client but if unable to verify will allow the client to communicate anonymously</li>
+                    		<li><b>Need Authentication</b> - Processor will reject communications from any client unless the client provides a certificate that is trusted by the TrustStore specified in the SSL Context Service</li>
+                    	</ul>
+                    </li>
+                    <li>Default value: No Authentication</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+        </ul>
+
+        <p>
+            <strong>Relationships:</strong>
+        </p>
+        <ul>
+            <li>success
+                <ul>
+                    <li>All FlowFiles that are created are routed to this relationship.</li>
+                </ul>
+            </li>
+        </ul>
+
+
+		<p>
+		<strong>See Also:</strong><br />
+		<a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a><br />
+		<a href="../org.apache.nifi.http.StandardHttpContextMap/index.html">StandardHttpContextMap</a><br />
+		<a href="../org.apache.nifi.ssl.StandardSSLContextService/index.html">StandardSSLContextService</a><br />
+		</p>
+
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpResponse/index.html
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpResponse/index.html b/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpResponse/index.html
new file mode 100644
index 0000000..70d76a6
--- /dev/null
+++ b/nar-bundles/standard-bundle/standard-processors/src/main/resources/docs/org.apache.nifi.processors.standard.HandleHttpResponse/index.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="en">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <head>
+        <meta charset="utf-8" />
+        <title>HandleHttpResponse</title>
+        <link rel="stylesheet" href="../../css/component-usage.css" type="text/css" />
+    </head>
+
+    <body>
+        <!-- Processor Documentation ================================================== -->
+        <h2>Description:</h2>
+        <p>
+            This processor responds to an HTTP request that was received by the  
+            <a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a> Processor.
+        </p>
+        
+        <p>
+        	The pairing of this Processor with a <a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a> Processor
+        	provides the ability to use NiFi to visually construct a web server that can carry out any functionality that is available
+        	through the existing Processors. For example, one could construct a Web-based front end to an SFTP Server by constructing a
+        	flow such as:
+        </p>
+        
+        <p>
+        	<a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a> -> 
+        	<a href="../org.apache.nifi.processors.standard.PutSFTP/index.html">PutSFTP</a> ->
+			<a href="index.html">HandleHttpResponse</a>
+        </p>
+        
+        <p>
+        	This Processor must be configured with the same &lt;HTTP Context Map&gt; service as the corresponding HandleHttpRequest Processor.
+        	Otherwise, all FlowFiles will be routed to the 'failure' relationship.
+        </p>
+        
+        <p>
+        	All FlowFiles must have an attribute named <code>http.context.identifier</code>. The value of this attribute is used to lookup
+        	the HTTP Response so that the proper message can be sent back to the requestor. If this attribute is missing, the FlowFile
+        	will be routed to 'failure.'
+        </p>
+        
+        
+        <p>
+            <strong>Properties:</strong>
+        </p>
+        <p>
+            In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. If a property has a default value, it is indicated. If a property supports the use of the NiFi Expression Language (or simply, "expression language"), that is also indicated.
+        </p>
+        <ul>
+            <li><strong>HTTP Status Code</strong>
+                <ul>
+                    <li>The HTTP Status Code to use when responding to the HTTP Request. See Section 10 of RFC 2616 for more information.</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: true</li>
+                </ul>
+            </li>
+            <li><strong>HTTP Context Map</strong>
+                <ul>
+                    <li>The HTTP Context Map Controller Service to use for caching the HTTP Request Information</li>
+                    <li>Default value: no default</li>
+                    <li>Supports expression language: false</li>
+                </ul>
+            </li>
+        </ul>
+
+        <p>
+            <strong>Relationships:</strong>
+        </p>
+        <ul>
+            <li>success
+                <ul>
+                    <li>If a message is successfully sent back to the requestor, the FlowFile is routed to this relationship.</li>
+                </ul>
+            </li>
+            
+            <li>failure
+            	<ul>
+            		<li>
+            			A FlowFile will be routed to failure under the following conditions:
+            			<ul>
+            				<li>The FlowFile does not have an <code>http.context.identifier</code> attribute</li>
+            				<li>The <code>http.context.identifier</code> attribute has a value that cannot be found in the HTTP Context Map</li>
+            				<li>The HTTP Status Code is not a number</li>
+            				<li>There was a communications failure when attempting to write the response to the requestor</li>
+            			</ul>
+            		</li>
+            	</ul>
+            </li>
+        </ul>
+
+		<p>
+		<strong>See Also:</strong><br />
+		<a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a><br />
+		<a href="../org.apache.nifi.http.StandardHttpContextMap/index.html">StandardHttpContextMap</a><br />
+		<a href="../org.apache.nifi.ssl.StandardSSLContextService/index.html">StandardSSLContextService</a><br />
+		</p>
+		
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-api/pom.xml
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-api/pom.xml b/nar-bundles/standard-services/http-context-map-api/pom.xml
new file mode 100644
index 0000000..cb847e2
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-api/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.nifi</groupId>
+    <artifactId>standard-services-parent</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+  </parent>
+  
+  <artifactId>http-context-map-api</artifactId>
+  
+  <name>http-context-map-api</name>
+  
+  <dependencies>
+  	<dependency>
+  		<groupId>org.apache.nifi</groupId>
+  		<artifactId>nifi-api</artifactId>
+  	</dependency>
+  	<dependency>
+  		<groupId>javax.servlet</groupId>
+  		<artifactId>javax.servlet-api</artifactId>
+  	</dependency>
+  </dependencies>
+  
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/HttpContextMap.java
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/HttpContextMap.java b/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/HttpContextMap.java
new file mode 100644
index 0000000..3e332b6
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/HttpContextMap.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.nifi.http;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.nifi.controller.ControllerService;
+
+
+/**
+ * <p>
+ * An interface that provides the capability of receiving an HTTP servlet request in one component
+ * and responding to that request in another component.
+ * </p>
+ * 
+ * <p>
+ * The intended flow is for the component receiving the HTTP request to register the request, response,
+ * and AsyncContext with a particular identifier via the 
+ * {@link #register(String, HttpServletRequest, HttpServletResponse, AsyncContext)}
+ * method. Another component is then able to obtain the response
+ * by providing that identifier to the {@link #getResponse(String)} method. After writing to the 
+ * HttpServletResponse, the transaction is to then be completed via the {@link #complete(String)} method.
+ * </p>
+ */
+public interface HttpContextMap extends ControllerService {
+
+    /**
+     * Registers an HttpServletRequest, HttpServletResponse, and the AsyncContext for a given identifier
+     * 
+     * @param identifier
+     * @param request
+     * @param response
+     * @param context
+     * 
+     * @throws IllegalStateException if the identifier is already registered
+     * @throws TooManyOutstandingRequestsException if too many requests have already been received and not processed
+     */
+    void register(String identifier, HttpServletRequest request, HttpServletResponse response, AsyncContext context) throws TooManyOutstandingRequestsException;
+    
+    /**
+     * Retrieves the HttpServletResponse for the given identifier, if it exists
+     * @param identifier
+     * @return the HttpServletResponse for the given identifier, or {@code null} if it does not exist
+     */
+    HttpServletResponse getResponse(String identifier);
+    
+    /**
+     * Marks the HTTP request/response for the given identifier as complete
+     * @param identifier
+     * 
+     * @throws IllegalStateException if the identifier is not registered to a valid AsyncContext
+     */
+    void complete(String identifier);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/TooManyOutstandingRequestsException.java
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/TooManyOutstandingRequestsException.java b/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/TooManyOutstandingRequestsException.java
new file mode 100644
index 0000000..b58c533
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-api/src/main/java/org/apache/nifi/http/TooManyOutstandingRequestsException.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.nifi.http;
+
+public class TooManyOutstandingRequestsException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public TooManyOutstandingRequestsException() {
+        super();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map-nar/pom.xml
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map-nar/pom.xml b/nar-bundles/standard-services/http-context-map-bundle/http-context-map-nar/pom.xml
new file mode 100644
index 0000000..7f0f917
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map-nar/pom.xml
@@ -0,0 +1,39 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>http-context-map-bundle</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+	
+    <artifactId>http-context-map-nar</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>HTTP Context Map Nar</name>
+    <packaging>nar</packaging>
+	
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>standard-services-api-nar</artifactId>
+            <type>nar</type>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.nifi</groupId>
+        	<artifactId>http-context-map</artifactId>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/pom.xml
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/pom.xml b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/pom.xml
new file mode 100644
index 0000000..125cc00
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/pom.xml
@@ -0,0 +1,42 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>http-context-map-bundle</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+	
+    <artifactId>http-context-map</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>HTTP Context Map</name>
+    <packaging>jar</packaging>
+	
+    <dependencies>
+    	<dependency>
+    		<groupId>org.apache.nifi</groupId>
+    		<artifactId>nifi-api</artifactId>
+    	</dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>http-context-map-api</artifactId>
+        </dependency>
+        <dependency>
+        	<groupId>javax.servlet</groupId>
+        	<artifactId>javax.servlet-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap$Wrapper.class
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap$Wrapper.class b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap$Wrapper.class
new file mode 100644
index 0000000..5b5a06c
Binary files /dev/null and b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap$Wrapper.class differ

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap.class
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap.class b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap.class
new file mode 100644
index 0000000..7de073e
Binary files /dev/null and b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/java/org/apache/nifi/http/StandardHttpContextMap.class differ

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
new file mode 100644
index 0000000..8c5721c
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -0,0 +1 @@
+org.apache.nifi.http.StandardHttpContextMap
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html
new file mode 100644
index 0000000..7e232b4
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/bin/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <head>
+        <meta charset="utf-8" />
+        <title>StandardHttpContextMap</title>
+
+        <link rel="stylesheet" href="../../css/component-usage.css" type="text/css" />
+    </head>
+
+    <body>
+		<h2>Description:</h2>
+		<p>
+			This is the standard implementation of the SSL Context Map. This service is used to provide
+			coordination between 
+			<a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a>
+			and
+			<a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a>
+			Processors.
+		</p>
+
+        <!-- Service Documentation ================================================== -->
+        <h2>Configuring the HTTP Context Map:</h2>
+        <p>
+            The <code>controller-services.xml</code> file is located in the NiFi <code>conf</code> 
+            directory. The user may set up any number of controller services within this file.
+        </p>
+
+        <p>
+        	This particular controller service requires no properties to be configured.
+            Below is an example of the template for a StandardHttpContextMap controller service.
+        </p>
+
+        <pre>
+&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
+&lt;services&gt;
+    &lt;service&gt;
+        &lt;identifier&gt;http-context-map&lt;/identifier&gt;
+        &lt;class&gt;org.apache.nifi.http.StandardHttpContextMap&lt;/class&gt;
+    &lt;/service&gt;
+&lt;/services&gt;
+        </pre>
+        
+        <p>
+		<strong>See Also:</strong><br />
+		<a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a><br />
+		<a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a><br />
+		</p>
+        
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/pom.xml
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/pom.xml b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/pom.xml
new file mode 100644
index 0000000..2b79d63
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/pom.xml
@@ -0,0 +1,46 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.nifi</groupId>
+        <artifactId>http-context-map-bundle</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+	
+    <artifactId>http-context-map</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>HTTP Context Map</name>
+    <packaging>jar</packaging>
+	
+    <dependencies>
+    	<dependency>
+    		<groupId>org.apache.nifi</groupId>
+    		<artifactId>nifi-api</artifactId>
+    	</dependency>
+    	<dependency>
+    		<groupId>org.apache.nifi</groupId>
+    		<artifactId>nifi-processor-utils</artifactId>
+    	</dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>http-context-map-api</artifactId>
+        </dependency>
+        <dependency>
+        	<groupId>javax.servlet</groupId>
+        	<artifactId>javax.servlet-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/java/org/apache/nifi/http/StandardHttpContextMap.java
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/java/org/apache/nifi/http/StandardHttpContextMap.java b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/java/org/apache/nifi/http/StandardHttpContextMap.java
new file mode 100644
index 0000000..47293d0
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/java/org/apache/nifi/http/StandardHttpContextMap.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.nifi.http;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.annotation.OnConfigured;
+import org.apache.nifi.processor.util.StandardValidators;
+
+public class StandardHttpContextMap extends AbstractControllerService implements HttpContextMap {
+    public static final PropertyDescriptor MAX_OUTSTANDING_REQUESTS = new PropertyDescriptor.Builder()
+        .name("Maximum Outstanding Requests")
+        .description("The maximum number of HTTP requests that can be outstanding at any one time. Any attempt to register an additional HTTP Request will cause an error")
+        .required(true)
+        .addValidator(StandardValidators.POSITIVE_INTEGER_VALIDATOR)
+        .defaultValue("5000")
+        .build();
+    
+    private final ConcurrentMap<String, Wrapper> wrapperMap = new ConcurrentHashMap<>();
+    
+    private volatile int maxSize = 5000;
+    
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return Collections.singletonList(MAX_OUTSTANDING_REQUESTS);
+    }
+    
+    @OnConfigured
+    public void onConfigured(final ConfigurationContext context) {
+        maxSize = context.getProperty(MAX_OUTSTANDING_REQUESTS).asInteger();
+    }
+    
+    @Override
+    public void register(final String identifier, final HttpServletRequest request, final HttpServletResponse response, final AsyncContext context) throws TooManyOutstandingRequestsException {
+        // TODO: Need to fail if there are too many already. Maybe add a configuration property for how many
+        // outstanding, with a default of say 5000
+        if ( wrapperMap.size() >= maxSize ) {
+            throw new TooManyOutstandingRequestsException();
+        }
+        final Wrapper wrapper = new Wrapper(request, response, context);
+        final Wrapper existing = wrapperMap.putIfAbsent(identifier, wrapper);
+        if ( existing != null ) {
+            throw new IllegalStateException("HTTP Request already registered with identifier " + identifier);
+        }
+    }
+
+    @Override
+    public HttpServletResponse getResponse(final String identifier) {
+        final Wrapper wrapper = wrapperMap.get(identifier);
+        if ( wrapper == null ) {
+            return null;
+        }
+        
+        return wrapper.getResponse();
+    }
+
+    @Override
+    public void complete(final String identifier) {
+        final Wrapper wrapper = wrapperMap.remove(identifier);
+        if ( wrapper == null ) {
+            throw new IllegalStateException("No HTTP Request registered with identifier " + identifier);
+        }
+        
+        wrapper.getAsync().complete();
+    }
+
+    private static class Wrapper {
+        @SuppressWarnings("unused")
+        private final HttpServletRequest request;
+        private final HttpServletResponse response;
+        private final AsyncContext async;
+        
+        public Wrapper(final HttpServletRequest request, final HttpServletResponse response, final AsyncContext async) {
+            this.request = request;
+            this.response = response;
+            this.async = async;
+        }
+
+        public HttpServletResponse getResponse() {
+            return response;
+        }
+
+        public AsyncContext getAsync() {
+            return async;
+        }
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
new file mode 100644
index 0000000..8c5721c
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -0,0 +1 @@
+org.apache.nifi.http.StandardHttpContextMap
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/d377a97a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html
----------------------------------------------------------------------
diff --git a/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html
new file mode 100644
index 0000000..5c8b83a
--- /dev/null
+++ b/nar-bundles/standard-services/http-context-map-bundle/http-context-map/src/main/resources/docs/org.apache.nifi.http.StandardHttpContextMap/index.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+    <!--
+      Licensed to the Apache Software Foundation (ASF) under one or more
+      contributor license agreements.  See the NOTICE file distributed with
+      this work for additional information regarding copyright ownership.
+      The ASF 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.
+    -->
+    <head>
+        <meta charset="utf-8" />
+        <title>StandardHttpContextMap</title>
+
+        <link rel="stylesheet" href="../../css/component-usage.css" type="text/css" />
+    </head>
+
+    <body>
+		<h2>Description:</h2>
+		<p>
+			This is the standard implementation of the SSL Context Map. This service is used to provide
+			coordination between 
+			<a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a>
+			and
+			<a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a>
+			Processors.
+		</p>
+
+        <!-- Service Documentation ================================================== -->
+        <h2>Configuring the HTTP Context Map:</h2>
+        <p>
+            The <code>controller-services.xml</code> file is located in the NiFi <code>conf</code> 
+            directory. The user may set up any number of controller services within this file.
+        </p>
+
+        <p>
+        	This controller service exposes a single property named <code>Maximum Outstanding Requests</code>.
+        	This property determines the maximum number of HTTP requests that can be outstanding at any one time. 
+        	Any attempt to register an additional HTTP Request will cause an error. The default value is 5000.
+            Below is an example of the template for a StandardHttpContextMap controller service.
+        </p>
+
+        <pre>
+&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
+&lt;services&gt;
+    &lt;service&gt;
+        &lt;identifier&gt;http-context-map&lt;/identifier&gt;
+        &lt;class&gt;org.apache.nifi.http.StandardHttpContextMap&lt;/class&gt;
+        &lt;property name="Maximum Outstanding Requests"&gt;5000&lt;/property&gt;
+    &lt;/service&gt;
+&lt;/services&gt;
+        </pre>
+        
+        <p>
+		<strong>See Also:</strong><br />
+		<a href="../org.apache.nifi.processors.standard.HandleHttpRequest/index.html">HandleHttpRequest</a><br />
+		<a href="../org.apache.nifi.processors.standard.HandleHttpResponse/index.html">HandleHttpResponse</a><br />
+		</p>
+        
+    </body>
+</html>


Mime
View raw message