View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.nio.ByteBuffer;
22  import java.nio.charset.StandardCharsets;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Locale;
26  import java.util.concurrent.Executor;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import org.eclipse.jetty.fcgi.FCGI;
30  import org.eclipse.jetty.http.HttpField;
31  import org.eclipse.jetty.http.HttpMethod;
32  import org.eclipse.jetty.http.HttpVersion;
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.HttpInput;
38  import org.eclipse.jetty.server.HttpTransport;
39  import org.eclipse.jetty.util.log.Log;
40  import org.eclipse.jetty.util.log.Logger;
41  
42  public class HttpChannelOverFCGI extends HttpChannel<ByteBuffer>
43  {
44      private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class);
45  
46      private final List<HttpField> fields = new ArrayList<>();
47      private final Dispatcher dispatcher;
48      private String method;
49      private String path;
50      private String query;
51      private String version;
52  
53      public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
54      {
55          super(connector, configuration, endPoint, transport, input);
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              fields.add(field);
71      }
72  
73      @Override
74      public boolean headerComplete()
75      {
76          String uri = path;
77          if (query != null && query.length() > 0)
78              uri += "?" + query;
79          startRequest(HttpMethod.fromString(method), method, ByteBuffer.wrap(uri.getBytes(StandardCharsets.UTF_8)),
80                  HttpVersion.fromString(version));
81  
82          for (HttpField fcgiField : fields)
83          {
84              HttpField httpField = convertHeader(fcgiField);
85              if (httpField != null)
86                  parsedHeader(httpField);
87          }
88  
89          return super.headerComplete();
90      }
91  
92      private HttpField convertHeader(HttpField field)
93      {
94          String name = field.getName();
95          if (name.startsWith("HTTP_"))
96          {
97              // Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding"
98              String[] parts = name.split("_");
99              StringBuilder httpName = new StringBuilder();
100             for (int i = 1; i < parts.length; ++i)
101             {
102                 if (i > 1)
103                     httpName.append("-");
104                 String part = parts[i];
105                 httpName.append(Character.toUpperCase(part.charAt(0)));
106                 httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH));
107             }
108             return new HttpField(httpName.toString(), field.getValue());
109         }
110         return null;
111     }
112 
113     protected void dispatch()
114     {
115         dispatcher.dispatch();
116     }
117 
118     private static class Dispatcher implements Runnable
119     {
120         private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
121         private final Executor executor;
122         private final Runnable runnable;
123 
124         private Dispatcher(Executor executor, Runnable runnable)
125         {
126             this.executor = executor;
127             this.runnable = runnable;
128         }
129 
130         public void dispatch()
131         {
132             while (true)
133             {
134                 State current = state.get();
135                 if (LOG.isDebugEnabled())
136                     LOG.debug("Dispatching, state={}", current);
137                 switch (current)
138                 {
139                     case IDLE:
140                     {
141                         if (!state.compareAndSet(current, State.DISPATCH))
142                             continue;
143                         executor.execute(this);
144                         return;
145                     }
146                     case DISPATCH:
147                     case EXECUTE:
148                     {
149                         if (state.compareAndSet(current, State.SCHEDULE))
150                             return;
151                         continue;
152                     }
153                     case SCHEDULE:
154                     {
155                         return;
156                     }
157                     default:
158                     {
159                         throw new IllegalStateException();
160                     }
161                 }
162             }
163         }
164 
165         @Override
166         public void run()
167         {
168             while (true)
169             {
170                 State current = state.get();
171                 if (LOG.isDebugEnabled())
172                     LOG.debug("Running, state={}", current);
173                 switch (current)
174                 {
175                     case DISPATCH:
176                     {
177                         if (state.compareAndSet(current, State.EXECUTE))
178                             runnable.run();
179                         continue;
180                     }
181                     case EXECUTE:
182                     {
183                         if (state.compareAndSet(current, State.IDLE))
184                             return;
185                         continue;
186                     }
187                     case SCHEDULE:
188                     {
189                         if (state.compareAndSet(current, State.DISPATCH))
190                             continue;
191                         throw new IllegalStateException();
192                     }
193                     default:
194                     {
195                         throw new IllegalStateException();
196                     }
197                 }
198             }
199         }
200 
201         private enum State
202         {
203             IDLE, DISPATCH, EXECUTE, SCHEDULE
204         }
205     }
206 }