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.getExecutor(), this);
57      }
58  
59      protected void header(HttpField field)
60      {
61          if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(field.getName()))
62              method = field.getValue();
63          else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(field.getName()))
64              path = field.getValue();
65          else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(field.getName()))
66              query = field.getValue();
67          else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(field.getName()))
68              version = field.getValue();
69          else
70              processField(field);
71      }
72  
73      private void processField(HttpField field)
74      {
75          HttpField httpField = convertHeader(field);
76          if (httpField != null)
77          {
78              fields.add(httpField);
79              if (HttpHeader.HOST.is(httpField.getName()))
80                  hostPort = (HostPortHttpField)httpField;
81          }
82      }
83  
84      public void onRequest()
85      {
86          String uri = path;
87          if (query != null && query.length() > 0)
88              uri += "?" + query;
89          // TODO https?
90          onRequest(new MetaData.Request(method, HttpScheme.HTTP.asString(), hostPort, uri, HttpVersion.fromString(version), fields,Long.MIN_VALUE));
91      }
92  
93      private HttpField convertHeader(HttpField field)
94      {
95          String name = field.getName();
96          if (name.startsWith("HTTP_"))
97          {
98              // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding"
99              String[] parts = name.split("_");
100             StringBuilder httpName = new StringBuilder();
101             for (int i = 1; i < parts.length; ++i)
102             {
103                 if (i > 1)
104                     httpName.append("-");
105                 String part = parts[i];
106                 httpName.append(Character.toUpperCase(part.charAt(0)));
107                 httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH));
108             }
109             String headerName = httpName.toString();
110             if (HttpHeader.HOST.is(headerName))
111                 return new HostPortHttpField(field.getValue());
112             else
113                 return new HttpField(httpName.toString(), field.getValue());
114         }
115         return null;
116     }
117 
118     protected void dispatch()
119     {
120         dispatcher.dispatch();
121     }
122 
123     private static class Dispatcher implements Runnable
124     {
125         private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
126         private final Executor executor;
127         private final Runnable runnable;
128 
129         private Dispatcher(Executor executor, Runnable runnable)
130         {
131             this.executor = executor;
132             this.runnable = runnable;
133         }
134 
135         public void dispatch()
136         {
137             while (true)
138             {
139                 State current = state.get();
140                 if (LOG.isDebugEnabled())
141                     LOG.debug("Dispatching, state={}", current);
142                 switch (current)
143                 {
144                     case IDLE:
145                     {
146                         if (!state.compareAndSet(current, State.DISPATCH))
147                             continue;
148                         executor.execute(this);
149                         return;
150                     }
151                     case DISPATCH:
152                     case EXECUTE:
153                     {
154                         if (state.compareAndSet(current, State.SCHEDULE))
155                             return;
156                         continue;
157                     }
158                     case SCHEDULE:
159                     {
160                         return;
161                     }
162                     default:
163                     {
164                         throw new IllegalStateException();
165                     }
166                 }
167             }
168         }
169 
170         @Override
171         public void run()
172         {
173             while (true)
174             {
175                 State current = state.get();
176                 if (LOG.isDebugEnabled())
177                     LOG.debug("Running, state={}", current);
178                 switch (current)
179                 {
180                     case DISPATCH:
181                     {
182                         if (state.compareAndSet(current, State.EXECUTE))
183                             runnable.run();
184                         continue;
185                     }
186                     case EXECUTE:
187                     {
188                         if (state.compareAndSet(current, State.IDLE))
189                             return;
190                         continue;
191                     }
192                     case SCHEDULE:
193                     {
194                         if (state.compareAndSet(current, State.DISPATCH))
195                             continue;
196                         throw new IllegalStateException();
197                     }
198                     default:
199                     {
200                         throw new IllegalStateException();
201                     }
202                 }
203             }
204         }
205 
206         private enum State
207         {
208             IDLE, DISPATCH, EXECUTE, SCHEDULE
209         }
210     }
211 }