1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import java.io.BufferedReader;
18 import java.io.Closeable;
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.io.OutputStream;
22 import java.io.PrintWriter;
23 import java.net.BindException;
24 import java.net.MalformedURLException;
25 import java.net.ServerSocket;
26 import java.net.Socket;
27 import java.net.SocketException;
28 import java.net.URL;
29 import java.nio.CharBuffer;
30 import java.nio.charset.StandardCharsets;
31 import java.util.HashSet;
32 import java.util.Set;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.htmlunit.MockWebConnection.RawResponseData;
38 import org.htmlunit.util.NameValuePair;
39
40
41
42
43
44
45
46
47
48 public class MiniServer extends Thread implements Closeable {
49 private static final Log LOG = LogFactory.getLog(MiniServer.class);
50
51 private final int port_;
52 private volatile boolean shutdown_ = false;
53 private final AtomicBoolean started_ = new AtomicBoolean(false);
54 private final MockWebConnection mockWebConnection_;
55 private volatile ServerSocket serverSocket_;
56 private String lastRequest_;
57
58 private static final Set<URL> DROP_REQUESTS = new HashSet<>();
59 private static final Set<URL> DROP_GET_REQUESTS = new HashSet<>();
60
61
62
63
64 public static void resetDropRequests() {
65 DROP_REQUESTS.clear();
66 DROP_GET_REQUESTS.clear();
67 }
68
69
70
71
72
73 public static void configureDropRequest(final URL url) {
74 DROP_REQUESTS.add(url);
75 }
76
77
78
79
80
81 public static void configureDropGetRequest(final URL url) {
82 DROP_GET_REQUESTS.add(url);
83 }
84
85
86
87
88
89
90 public MiniServer(final int port, final MockWebConnection mockWebConnection) {
91 port_ = port;
92 mockWebConnection_ = mockWebConnection;
93 setDaemon(true);
94 }
95
96 @Override
97 public void run() {
98 try {
99 final long maxWait = System.currentTimeMillis() + WebServerTestCase.BIND_TIMEOUT;
100 while (true) {
101 try {
102 serverSocket_ = new ServerSocket(port_);
103 break;
104 }
105 catch (final BindException e) {
106 if (System.currentTimeMillis() > maxWait) {
107 throw (BindException) new BindException("Port " + port_ + " is already in use").initCause(e);
108 }
109 try {
110 Thread.sleep(200);
111 }
112 catch (final InterruptedException ex) {
113 LOG.error(ex.getMessage(), ex);
114 }
115 }
116 }
117
118 started_.set(true);
119 LOG.info("Starting listening on port " + port_);
120 while (!shutdown_) {
121 try (Socket s = serverSocket_.accept()) {
122 try (BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()))) {
123
124 final CharBuffer cb = CharBuffer.allocate(5000);
125 br.read(cb);
126 cb.flip();
127 final String in = cb.toString();
128 cb.rewind();
129
130 RawResponseData responseData = null;
131 final WebRequest request = parseRequest(in);
132
133
134 try {
135 if (request != null) {
136 responseData = mockWebConnection_.getRawResponse(request);
137 }
138 }
139 catch (final IllegalStateException e) {
140 LOG.error(e);
141 }
142
143 if (request == null
144 || (DROP_REQUESTS.contains(request.getUrl())
145 || (request.getHttpMethod() == HttpMethod.GET
146 && DROP_GET_REQUESTS.contains(request.getUrl())))) {
147 responseData = null;
148 }
149
150 if (responseData == null) {
151 LOG.info("Closing impolitely in & output streams");
152 s.getOutputStream().close();
153 }
154 else if (responseData.getByteContent() != null) {
155 try (OutputStream os = s.getOutputStream()) {
156 os.write(("HTTP/1.0 " + responseData.getStatusCode() + " "
157 + responseData.getStatusMessage())
158 .getBytes(StandardCharsets.US_ASCII));
159 os.write("\n".getBytes(StandardCharsets.US_ASCII));
160 for (final NameValuePair header : responseData.getHeaders()) {
161 os.write((header.getName() + ": "
162 + header.getValue()).getBytes(StandardCharsets.US_ASCII));
163 os.write("\n".getBytes(StandardCharsets.US_ASCII));
164 }
165 os.write("\n".getBytes(StandardCharsets.US_ASCII));
166 os.write(responseData.getByteContent(), 0, responseData.getByteContent().length);
167
168 os.flush();
169 }
170 }
171 else {
172 try (PrintWriter pw = new PrintWriter(s.getOutputStream())) {
173 pw.println("HTTP/1.0 " + responseData.getStatusCode() + " "
174 + responseData.getStatusMessage());
175 for (final NameValuePair header : responseData.getHeaders()) {
176 pw.println(header.getName() + ": " + header.getValue());
177 }
178 pw.println();
179 pw.println(responseData.getStringContent());
180 pw.println();
181 pw.flush();
182 }
183 }
184 }
185 }
186 }
187 }
188 catch (final SocketException e) {
189 if (!shutdown_) {
190 LOG.error(e);
191 }
192 }
193 catch (final IOException e) {
194 LOG.error(e);
195 }
196 finally {
197 LOG.info("Finished listening on port " + port_);
198 }
199 }
200
201 private WebRequest parseRequest(final String request) {
202 final int firstSpace = request.indexOf(' ');
203 final int secondSpace = request.indexOf(' ', firstSpace + 1);
204
205 HttpMethod submitMethod = HttpMethod.GET;
206 final String methodText = request.substring(0, firstSpace);
207 if ("OPTIONS".equalsIgnoreCase(methodText)) {
208 submitMethod = HttpMethod.OPTIONS;
209 }
210 else if ("POST".equalsIgnoreCase(methodText)) {
211 submitMethod = HttpMethod.POST;
212 }
213
214 final String requestedPath = request.substring(firstSpace + 1, secondSpace);
215 if ("/favicon.ico".equals(requestedPath)) {
216 LOG.debug("Skipping /favicon.ico");
217 return null;
218 }
219 try {
220 final URL url = new URL("http://localhost:" + port_ + requestedPath);
221 lastRequest_ = request;
222 return new WebRequest(url, submitMethod);
223 }
224 catch (final MalformedURLException e) {
225 LOG.error(e);
226 return null;
227 }
228 }
229
230
231
232
233 public String getLastRequest() {
234 return lastRequest_;
235 }
236
237
238
239
240
241
242 @Override
243 public void close() throws IOException {
244 shutdown_ = true;
245 if (serverSocket_ != null) {
246 serverSocket_.close();
247 }
248 interrupt();
249 try {
250 join(5000);
251 }
252 catch (final InterruptedException e) {
253 throw new IOException("MoniServer join() failed", e);
254 }
255 }
256
257 @Override
258 public synchronized void start() {
259 super.start();
260
261
262
263 for (int i = 0; i < 10; i++) {
264 if (!started_.get()) {
265 try {
266 Thread.sleep(100);
267 }
268 catch (final InterruptedException e) {
269 throw new RuntimeException(e);
270 }
271 }
272 }
273 }
274 }