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                 LOG.debug("Dispatching, state={}", current);
136                 switch (current)
137                 {
138                     case IDLE:
139                     {
140                         if (!state.compareAndSet(current, State.DISPATCH))
141                             continue;
142                         executor.execute(this);
143                         return;
144                     }
145                     case DISPATCH:
146                     case EXECUTE:
147                     {
148                         if (state.compareAndSet(current, State.SCHEDULE))
149                             return;
150                         continue;
151                     }
152                     case SCHEDULE:
153                     {
154                         return;
155                     }
156                     default:
157                     {
158                         throw new IllegalStateException();
159                     }
160                 }
161             }
162         }
163 
164         @Override
165         public void run()
166         {
167             while (true)
168             {
169                 State current = state.get();
170                 LOG.debug("Running, state={}", current);
171                 switch (current)
172                 {
173                     case DISPATCH:
174                     {
175                         if (state.compareAndSet(current, State.EXECUTE))
176                             runnable.run();
177                         continue;
178                     }
179                     case EXECUTE:
180                     {
181                         if (state.compareAndSet(current, State.IDLE))
182                             return;
183                         continue;
184                     }
185                     case SCHEDULE:
186                     {
187                         if (state.compareAndSet(current, State.DISPATCH))
188                             continue;
189                         throw new IllegalStateException();
190                     }
191                     default:
192                     {
193                         throw new IllegalStateException();
194                     }
195                 }
196             }
197         }
198 
199         private enum State
200         {
201             IDLE, DISPATCH, EXECUTE, SCHEDULE
202         }
203     }
204 }