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 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 }