use of org.apache.nifi.http.HttpContextMap in project nifi by apache.
the class HandleHttpResponse method onTrigger.
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
FlowFile flowFile = session.get();
if (flowFile == null) {
return;
}
final StopWatch stopWatch = new StopWatch(true);
final String contextIdentifier = flowFile.getAttribute(HTTPUtils.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 '" + HTTPUtils.HTTP_CONTEXT_ID + "' 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 respond to HTTP request for {} because status code was '{}', which is not a valid number", new Object[] { flowFile, statusCodeValue });
return;
}
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 '{}' attribute of {} but could not find an HTTP Response Object for this identifier", new Object[] { flowFile, HTTPUtils.HTTP_CONTEXT_ID, contextIdentifier });
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 ProcessException e) {
session.transfer(flowFile, REL_FAILURE);
getLogger().error("Failed to respond to HTTP request for {} due to {}", new Object[] { flowFile, e });
contextMap.complete(contextIdentifier);
return;
} catch (final Exception e) {
session.transfer(flowFile, REL_FAILURE);
getLogger().error("Failed to respond to HTTP request for {} due to {}", new Object[] { flowFile, e });
return;
}
try {
contextMap.complete(contextIdentifier);
} catch (final IllegalStateException ise) {
getLogger().error("Failed to complete HTTP Transaction for {} due to {}", new Object[] { flowFile, ise });
session.transfer(flowFile, REL_FAILURE);
return;
}
session.getProvenanceReporter().send(flowFile, HTTPUtils.getURI(flowFile.getAttributes()), stopWatch.getElapsed(TimeUnit.MILLISECONDS));
session.transfer(flowFile, REL_SUCCESS);
getLogger().info("Successfully responded to HTTP Request for {} with status code {}", new Object[] { flowFile, statusCode });
}
use of org.apache.nifi.http.HttpContextMap in project nifi by apache.
the class HandleHttpRequest method onTrigger.
@Override
public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
try {
if (!initialized.get()) {
initializeServer(context);
}
} catch (Exception e) {
context.yield();
try {
// shutdown to release any resources allocated during the failed initialization
shutdown();
} catch (final Exception shutdownException) {
getLogger().debug("Failed to shutdown following a failed initialization: " + shutdownException);
}
throw new ProcessException("Failed to initialize the server", e);
}
HttpRequestContainer container;
try {
container = containerQueue.poll(2, TimeUnit.MILLISECONDS);
} catch (final InterruptedException e1) {
Thread.currentThread().interrupt();
return;
}
if (container == null) {
return;
}
final long start = System.nanoTime();
final HttpServletRequest request = container.getRequest();
FlowFile flowFile = session.create();
try (OutputStream flowFileOut = session.write(flowFile)) {
StreamUtils.copy(request.getInputStream(), flowFileOut);
} catch (final IOException e) {
// There may be many reasons which can produce an IOException on the HTTP stream and in some of them, eg.
// bad requests, the connection to the client is not closed. In order to address also these cases, we try
// and answer with a BAD_REQUEST, which lets the client know that the request has not been correctly
// processed and makes it aware that the connection can be closed.
getLogger().error("Failed to receive content from HTTP Request from {} due to {}", new Object[] { request.getRemoteAddr(), e });
session.remove(flowFile);
try {
HttpServletResponse response = container.getResponse();
response.sendError(Status.BAD_REQUEST.getStatusCode());
response.flushBuffer();
container.getContext().complete();
} catch (final IOException ioe) {
getLogger().warn("Failed to send HTTP response to {} due to {}", new Object[] { request.getRemoteAddr(), ioe });
}
return;
}
final String charset = request.getCharacterEncoding() == null ? context.getProperty(URL_CHARACTER_SET).getValue() : request.getCharacterEncoding();
final String contextIdentifier = UUID.randomUUID().toString();
final Map<String, String> attributes = new HashMap<>();
try {
putAttribute(attributes, HTTPUtils.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.local.addr", request.getLocalAddr());
putAttribute(attributes, HTTPUtils.HTTP_LOCAL_NAME, request.getLocalName());
final String queryString = request.getQueryString();
if (queryString != null) {
putAttribute(attributes, "http.query.string", URLDecoder.decode(queryString, charset));
}
putAttribute(attributes, HTTPUtils.HTTP_REMOTE_HOST, request.getRemoteHost());
putAttribute(attributes, "http.remote.addr", request.getRemoteAddr());
putAttribute(attributes, "http.remote.user", request.getRemoteUser());
putAttribute(attributes, "http.protocol", request.getProtocol());
putAttribute(attributes, HTTPUtils.HTTP_REQUEST_URI, request.getRequestURI());
putAttribute(attributes, "http.request.url", request.getRequestURL().toString());
putAttribute(attributes, "http.auth.type", request.getAuthType());
putAttribute(attributes, "http.requested.session.id", request.getRequestedSessionId());
final DispatcherType dispatcherType = request.getDispatcherType();
if (dispatcherType != null) {
putAttribute(attributes, "http.dispatcher.type", dispatcherType.name());
}
putAttribute(attributes, "http.character.encoding", request.getCharacterEncoding());
putAttribute(attributes, "http.locale", request.getLocale());
putAttribute(attributes, "http.server.name", request.getServerName());
putAttribute(attributes, HTTPUtils.HTTP_PORT, request.getServerPort());
final Enumeration<String> paramEnumeration = request.getParameterNames();
while (paramEnumeration.hasMoreElements()) {
final String paramName = paramEnumeration.nextElement();
final String value = request.getParameter(paramName);
attributes.put("http.param." + paramName, value);
}
final Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (final Cookie cookie : cookies) {
final String name = cookie.getName();
final String cookiePrefix = "http.cookie." + name + ".";
attributes.put(cookiePrefix + "value", cookie.getValue());
attributes.put(cookiePrefix + "domain", cookie.getDomain());
attributes.put(cookiePrefix + "path", cookie.getPath());
attributes.put(cookiePrefix + "max.age", String.valueOf(cookie.getMaxAge()));
attributes.put(cookiePrefix + "version", String.valueOf(cookie.getVersion()));
attributes.put(cookiePrefix + "secure", String.valueOf(cookie.getSecure()));
}
}
if (queryString != null) {
final String[] params = URL_QUERY_PARAM_DELIMITER.split(queryString);
for (final String keyValueString : params) {
final int indexOf = keyValueString.indexOf("=");
if (indexOf < 0) {
// no =, then it's just a key with no value
attributes.put("http.query.param." + URLDecoder.decode(keyValueString, charset), "");
} else {
final String key = keyValueString.substring(0, indexOf);
final String value;
if (indexOf == keyValueString.length() - 1) {
value = "";
} else {
value = keyValueString.substring(indexOf + 1);
}
attributes.put("http.query.param." + URLDecoder.decode(key, charset), URLDecoder.decode(value, charset));
}
}
}
} catch (final UnsupportedEncodingException uee) {
// won't happen because charset has been validated
throw new ProcessException("Invalid character encoding", uee);
}
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");
final String subjectDn;
if (certs != null && certs.length > 0) {
final X509Certificate cert = certs[0];
subjectDn = cert.getSubjectDN().getName();
final String issuerDn = cert.getIssuerDN().getName();
putAttribute(attributes, HTTPUtils.HTTP_SSL_CERT, subjectDn);
putAttribute(attributes, "http.issuer.dn", issuerDn);
} else {
subjectDn = null;
}
flowFile = session.putAllAttributes(flowFile, attributes);
final HttpContextMap contextMap = context.getProperty(HTTP_CONTEXT_MAP).asControllerService(HttpContextMap.class);
final boolean registered = contextMap.register(contextIdentifier, request, container.getResponse(), container.getContext());
if (!registered) {
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 });
}
session.remove(flowFile);
return;
}
final long receiveMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
session.getProvenanceReporter().receive(flowFile, HTTPUtils.getURI(attributes), "Received from " + request.getRemoteAddr() + (subjectDn == null ? "" : " with DN=" + subjectDn), receiveMillis);
session.transfer(flowFile, REL_SUCCESS);
getLogger().info("Transferring {} to 'success'; received from {}", new Object[] { flowFile, request.getRemoteAddr() });
}
use of org.apache.nifi.http.HttpContextMap in project nifi by apache.
the class HandleHttpRequest method initializeServer.
private synchronized void initializeServer(final ProcessContext context) throws Exception {
if (initialized.get()) {
return;
}
this.containerQueue = new LinkedBlockingQueue<>(context.getProperty(CONTAINER_QUEUE_SIZE).asInteger());
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 HttpContextMap httpContextMap = context.getProperty(HTTP_CONTEXT_MAP).asControllerService(HttpContextMap.class);
final long requestTimeout = httpContextMap.getRequestTimeout(TimeUnit.MILLISECONDS);
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;
}
}
// If destination queues full, send back a 503: Service Unavailable.
if (context.getAvailableRelationships().isEmpty()) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
// 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();
async.setTimeout(requestTimeout);
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());
initialized.set(true);
}
Aggregations