1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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 }