| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.fcgi.server; |
| |
| import java.util.Locale; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.eclipse.jetty.fcgi.FCGI; |
| import org.eclipse.jetty.http.HostPortHttpField; |
| import org.eclipse.jetty.http.HttpField; |
| import org.eclipse.jetty.http.HttpFields; |
| import org.eclipse.jetty.http.HttpHeader; |
| import org.eclipse.jetty.http.HttpScheme; |
| import org.eclipse.jetty.http.HttpVersion; |
| import org.eclipse.jetty.http.MetaData; |
| import org.eclipse.jetty.io.EndPoint; |
| import org.eclipse.jetty.server.Connector; |
| import org.eclipse.jetty.server.HttpChannel; |
| import org.eclipse.jetty.server.HttpConfiguration; |
| import org.eclipse.jetty.server.HttpTransport; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| public class HttpChannelOverFCGI extends HttpChannel |
| { |
| private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class); |
| |
| private final HttpFields fields = new HttpFields(); |
| private final Dispatcher dispatcher; |
| private String method; |
| private String path; |
| private String query; |
| private String version; |
| private HostPortHttpField hostPort; |
| |
| public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport) |
| { |
| super(connector, configuration, endPoint, transport); |
| this.dispatcher = new Dispatcher(connector.getServer().getThreadPool(), this); |
| } |
| |
| protected void header(HttpField field) |
| { |
| String name = field.getName(); |
| String value = field.getValue(); |
| getRequest().setAttribute(name, value); |
| if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(name)) |
| method = value; |
| else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(name)) |
| path = value; |
| else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(name)) |
| query = value; |
| else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(name)) |
| version = value; |
| else |
| processField(field); |
| } |
| |
| private void processField(HttpField field) |
| { |
| HttpField httpField = convertHeader(field); |
| if (httpField != null) |
| { |
| fields.add(httpField); |
| if (HttpHeader.HOST.is(httpField.getName())) |
| hostPort = (HostPortHttpField)httpField; |
| } |
| } |
| |
| public void onRequest() |
| { |
| String uri = path; |
| if (query != null && query.length() > 0) |
| uri += "?" + query; |
| // TODO https? |
| onRequest(new MetaData.Request(method, HttpScheme.HTTP.asString(), hostPort, uri, HttpVersion.fromString(version), fields,Long.MIN_VALUE)); |
| } |
| |
| private HttpField convertHeader(HttpField field) |
| { |
| String name = field.getName(); |
| if (name.startsWith("HTTP_")) |
| { |
| // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding" |
| String[] parts = name.split("_"); |
| StringBuilder httpName = new StringBuilder(); |
| for (int i = 1; i < parts.length; ++i) |
| { |
| if (i > 1) |
| httpName.append("-"); |
| String part = parts[i]; |
| httpName.append(Character.toUpperCase(part.charAt(0))); |
| httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH)); |
| } |
| String headerName = httpName.toString(); |
| String value = field.getValue(); |
| if (HttpHeader.HOST.is(headerName)) |
| return new HostPortHttpField(value); |
| else |
| return new HttpField(headerName, value); |
| } |
| return null; |
| } |
| |
| protected void dispatch() |
| { |
| dispatcher.dispatch(); |
| } |
| |
| private static class Dispatcher implements Runnable |
| { |
| private final AtomicReference<State> state = new AtomicReference<>(State.IDLE); |
| private final Executor executor; |
| private final Runnable runnable; |
| |
| private Dispatcher(Executor executor, Runnable runnable) |
| { |
| this.executor = executor; |
| this.runnable = runnable; |
| } |
| |
| public void dispatch() |
| { |
| while (true) |
| { |
| State current = state.get(); |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Dispatching, state={}", current); |
| switch (current) |
| { |
| case IDLE: |
| { |
| if (!state.compareAndSet(current, State.DISPATCH)) |
| continue; |
| executor.execute(this); |
| return; |
| } |
| case DISPATCH: |
| case EXECUTE: |
| { |
| if (state.compareAndSet(current, State.SCHEDULE)) |
| return; |
| continue; |
| } |
| case SCHEDULE: |
| { |
| return; |
| } |
| default: |
| { |
| throw new IllegalStateException(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void run() |
| { |
| while (true) |
| { |
| State current = state.get(); |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Running, state={}", current); |
| switch (current) |
| { |
| case DISPATCH: |
| { |
| if (state.compareAndSet(current, State.EXECUTE)) |
| runnable.run(); |
| continue; |
| } |
| case EXECUTE: |
| { |
| if (state.compareAndSet(current, State.IDLE)) |
| return; |
| continue; |
| } |
| case SCHEDULE: |
| { |
| if (state.compareAndSet(current, State.DISPATCH)) |
| continue; |
| throw new IllegalStateException(); |
| } |
| default: |
| { |
| throw new IllegalStateException(); |
| } |
| } |
| } |
| } |
| |
| private enum State |
| { |
| IDLE, DISPATCH, EXECUTE, SCHEDULE |
| } |
| } |
| } |