1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.util;
16
17 import java.io.CharArrayWriter;
18 import java.io.Closeable;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 import java.net.BindException;
23 import java.net.InetSocketAddress;
24 import java.net.ServerSocket;
25 import java.net.Socket;
26 import java.net.SocketException;
27 import java.nio.charset.Charset;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.List;
32
33 import org.apache.commons.io.IOUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.htmlunit.HttpHeader;
36 import org.htmlunit.WebServerTestCase;
37 import org.htmlunit.WebTestCase;
38
39
40
41
42
43
44
45
46 public class PrimitiveWebServer implements Closeable {
47
48 private final int port_;
49 private final String firstResponse_;
50 private final String secondResponse_;
51 private final String otherResponse_;
52 private ServerSocket server_;
53 private Charset charset_ = StandardCharsets.ISO_8859_1;
54 private final List<String> requests_ = Collections.synchronizedList(new ArrayList<>());
55
56
57
58
59
60
61
62
63
64 public PrimitiveWebServer(final Charset charset, final String firstResponse, final String otherResponse)
65 throws Exception {
66 this(charset, firstResponse, null, otherResponse);
67 }
68
69
70
71
72
73
74
75
76
77
78 public PrimitiveWebServer(final Charset charset, final String firstResponse, final String secondResponse, final String otherResponse)
79 throws Exception {
80 this(WebTestCase.PORT_PRIMITIVE_SERVER, charset, firstResponse, secondResponse, otherResponse);
81 }
82
83
84
85
86
87
88
89
90
91
92
93 public PrimitiveWebServer(final int port, final Charset charset, final String firstResponse, final String secondResponse, final String otherResponse)
94 throws Exception {
95 port_ = port;
96 firstResponse_ = firstResponse;
97 secondResponse_ = secondResponse;
98 otherResponse_ = otherResponse;
99 if (charset != null) {
100 charset_ = charset;
101 }
102
103 start();
104 }
105
106
107
108
109
110 private void start() throws Exception {
111 server_ = new ServerSocket();
112 server_.setReuseAddress(true);
113
114 final long maxWait = System.currentTimeMillis() + WebServerTestCase.BIND_TIMEOUT;
115
116 while (true) {
117 try {
118 server_.bind(new InetSocketAddress(port_));
119 break;
120 }
121 catch (final BindException e) {
122 if (System.currentTimeMillis() > maxWait) {
123 throw (BindException) new BindException("Port " + port_ + " is already in use").initCause(e);
124 }
125 Thread.sleep(200);
126 }
127 }
128
129 new Thread(() -> {
130 int responseCount = 0;
131 try {
132 while (true) {
133 try (final Socket socket = server_.accept()) {
134 final InputStream in = socket.getInputStream();
135 final CharArrayWriter writer = new CharArrayWriter();
136
137 String requestString = writer.toString();
138 int i;
139
140 while ((i = in.read()) != -1) {
141 writer.append((char) i);
142 requestString = writer.toString();
143
144 if (i == '\n' && requestString.endsWith("\r\n\r\n")) {
145 break;
146 }
147 }
148 final int contentLengthPos = StringUtils.indexOfIgnoreCase(requestString, HttpHeader.CONTENT_LENGTH);
149 if (contentLengthPos > -1) {
150 final int endPos = requestString.indexOf('\n', contentLengthPos + 16);
151 final String toParse = requestString.substring(contentLengthPos + 16, endPos);
152 final int contentLength = Integer.parseInt(toParse.trim());
153
154 if (contentLength > 0) {
155 final byte[] charArray = new byte[contentLength];
156 IOUtils.readFully(in, charArray);
157 requestString += new String(charArray);
158 }
159 }
160
161 final String response;
162 if (requestString.isEmpty()
163 || requestString.contains("/favicon.ico")) {
164 response = "HTTP/1.1 404 Not Found\r\n"
165 + "Content-Length: 0\r\n"
166 + "Connection: close\r\n"
167 + "\r\n";
168 }
169 else {
170 requests_.add(requestString);
171 if (responseCount == 0 || otherResponse_ == null) {
172 response = firstResponse_;
173 }
174 else if (responseCount == 1 && secondResponse_ != null) {
175 response = secondResponse_;
176 }
177 else {
178 response = otherResponse_;
179 }
180 responseCount++;
181 }
182
183 final OutputStream out = socket.getOutputStream();
184 final int headPos = response.indexOf("\r\n\r\n");
185 out.write(response.substring(0, headPos + 4).getBytes(StandardCharsets.US_ASCII));
186 out.write(response.substring(headPos + 4).getBytes(charset_));
187 out.flush();
188 }
189 }
190 }
191 catch (final SocketException e) {
192
193 }
194 catch (final Exception e) {
195 throw new RuntimeException(e);
196 }
197 }).start();
198 }
199
200
201
202
203
204 @Override
205 public void close() throws IOException {
206 server_.close();
207 }
208
209
210
211
212
213 public List<String> getRequests() {
214 return requests_;
215 }
216
217
218
219
220
221 public int getPort() {
222 return port_;
223 }
224
225
226
227
228 public void clearRequests() {
229 requests_.clear();
230 }
231 }