View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.fcgi.server;
20  
21  import java.util.Locale;
22  import java.util.concurrent.Executor;
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  import org.eclipse.jetty.fcgi.FCGI;
26  import org.eclipse.jetty.http.HostPortHttpField;
27  import org.eclipse.jetty.http.HttpField;
28  import org.eclipse.jetty.http.HttpFields;
29  import org.eclipse.jetty.http.HttpHeader;
30  import org.eclipse.jetty.http.HttpScheme;
31  import org.eclipse.jetty.http.HttpVersion;
32  import org.eclipse.jetty.http.MetaData;
33  import org.eclipse.jetty.io.EndPoint;
34  import org.eclipse.jetty.server.Connector;
35  import org.eclipse.jetty.server.HttpChannel;
36  import org.eclipse.jetty.server.HttpConfiguration;
37  import org.eclipse.jetty.server.HttpTransport;
38  import org.eclipse.jetty.util.log.Log;
39  import org.eclipse.jetty.util.log.Logger;
40  
41  public class HttpChannelOverFCGI extends HttpChannel
42  {
43      private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class);
44  
45      private final HttpFields fields = new HttpFields();
46      private final Dispatcher dispatcher;
47      private String method;
48      private String path;
49      private String query;
50      private String version;
51      private HostPortHttpField hostPort;
52  
53      public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport)
54      {
55          super(connector, configuration, endPoint, transport);
56          this.dispatcher = new Dispatcher(connector.getServer().getThreadPool(), this);
57      }
58  
59      protected void header(HttpField field)
60      {
61          String name = field.getName();
62          String value = field.getValue();
63          getRequest().setAttribute(name, value);
64          if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(name))
65              method = value;
66          else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(name))
67              path = value;
68          else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(name))
69              query = value;
70          else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(name))
71              version = value;
72          else
73              processField(field);
74      }
75  
76      private void processField(HttpField field)
77      {
78          HttpField httpField = convertHeader(field);
79          if (httpField != null)
80          {
81              fields.add(httpField);
82              if (HttpHeader.HOST.is(httpField.getName()))
83                  hostPort = (HostPortHttpField)httpField;
84          }
85      }
86  
87      public void onRequest()
88      {
89          String uri = path;
90          if (query != null && query.length() > 0)
91              uri += "?" + query;
92          // TODO https?
93          onRequest(new MetaData.Request(method, HttpScheme.HTTP.asString(), hostPort, uri, HttpVersion.fromString(version), fields,Long.MIN_VALUE));
94      }
95  
96      private HttpField convertHeader(HttpField field)
97      {
98          String name = field.getName();
99          if (name.startsWith("HTTP_"))
100         {
101             // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding"
102             String[] parts = name.split("_");
103             StringBuilder httpName = new StringBuilder();
104             for (int i = 1; i < parts.length; ++i)
105             {
106                 if (i > 1)
107                     httpName.append("-");
108                 String part = parts[i];
109                 httpName.append(Character.toUpperCase(part.charAt(0)));
110                 httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH));
111             }
112             String headerName = httpName.toString();
113             String value = field.getValue();
114             if (HttpHeader.HOST.is(headerName))
115                 return new HostPortHttpField(value);
116             else
117                 return new HttpField(headerName, value);
118         }
119         return null;
120     }
121 
122     protected void dispatch()
123     {
124         dispatcher.dispatch();
125     }
126 
127     private static class Dispatcher implements Runnable
128     {
129         private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
130         private final Executor executor;
131         private final Runnable runnable;
132 
133         private Dispatcher(Executor executor, Runnable runnable)
134         {
135             this.executor = executor;
136             this.runnable = runnable;
137         }
138 
139         public void dispatch()
140         {
141             while (true)
142             {
143                 State current = state.get();
144                 if (LOG.isDebugEnabled())
145                     LOG.debug("Dispatching, state={}", current);
146                 switch (current)
147                 {
148                     case IDLE:
149                     {
150                         if (!state.compareAndSet(current, State.DISPATCH))
151                             continue;
152                         executor.execute(this);
153                         return;
154                     }
155                     case DISPATCH:
156                     case EXECUTE:
157                     {
158                         if (state.compareAndSet(current, State.SCHEDULE))
159                             return;
160                         continue;
161                     }
162                     case SCHEDULE:
163                     {
164                         return;
165                     }
166                     default:
167                     {
168                         throw new IllegalStateException();
169                     }
170                 }
171             }
172         }
173 
174         @Override
175         public void run()
176         {
177             while (true)
178             {
179                 State current = state.get();
180                 if (LOG.isDebugEnabled())
181                     LOG.debug("Running, state={}", current);
182                 switch (current)
183                 {
184                     case DISPATCH:
185                     {
186                         if (state.compareAndSet(current, State.EXECUTE))
187                             runnable.run();
188                         continue;
189                     }
190                     case EXECUTE:
191                     {
192                         if (state.compareAndSet(current, State.IDLE))
193                             return;
194                         continue;
195                     }
196                     case SCHEDULE:
197                     {
198                         if (state.compareAndSet(current, State.DISPATCH))
199                             continue;
200                         throw new IllegalStateException();
201                     }
202                     default:
203                     {
204                         throw new IllegalStateException();
205                     }
206                 }
207             }
208         }
209 
210         private enum State
211         {
212             IDLE, DISPATCH, EXECUTE, SCHEDULE
213         }
214     }
215 }