20
20
import static java .net .HttpURLConnection .HTTP_NO_CONTENT ;
21
21
import static java .net .HttpURLConnection .HTTP_OK ;
22
22
23
+ import com .google .api .client .http .HttpMediaType ;
23
24
import com .google .api .client .json .JsonFactory ;
24
25
import com .google .api .client .json .jackson .JacksonFactory ;
25
26
import com .google .api .services .dns .model .Change ;
43
44
import com .sun .net .httpserver .HttpHandler ;
44
45
import com .sun .net .httpserver .HttpServer ;
45
46
47
+ import org .apache .commons .fileupload .MultipartStream ;
46
48
import org .joda .time .format .ISODateTimeFormat ;
47
49
50
+ import java .io .ByteArrayOutputStream ;
48
51
import java .io .IOException ;
49
52
import java .io .InputStream ;
50
53
import java .io .OutputStream ;
51
54
import java .math .BigInteger ;
52
55
import java .net .InetSocketAddress ;
56
+ import java .net .Socket ;
53
57
import java .net .URI ;
54
58
import java .net .URISyntaxException ;
55
59
import java .nio .charset .StandardCharsets ;
62
66
import java .util .NavigableMap ;
63
67
import java .util .NavigableSet ;
64
68
import java .util .Random ;
69
+ import java .util .Scanner ;
65
70
import java .util .Set ;
66
71
import java .util .SortedMap ;
67
72
import java .util .TreeMap ;
@@ -112,6 +117,9 @@ public class LocalDnsHelper {
112
117
private static final ScheduledExecutorService EXECUTORS =
113
118
Executors .newScheduledThreadPool (2 , Executors .defaultThreadFactory ());
114
119
private static final String PROJECT_ID = "dummyprojectid" ;
120
+ private static final String RESPONSE_BOUNDARY = "____THIS_IS_HELPERS_BOUNDARY____" ;
121
+ private static final String RESPONSE_SEPARATOR = "--" + RESPONSE_BOUNDARY + "\r \n " ;
122
+ private static final String RESPONSE_END = "--" + RESPONSE_BOUNDARY + "--\r \n \r \n " ;
115
123
116
124
static {
117
125
try {
@@ -138,7 +146,8 @@ private enum CallRegex {
138
146
ZONE_GET ("GET" , CONTEXT + "/[^/]+/managedZones/[^/]+" ),
139
147
ZONE_LIST ("GET" , CONTEXT + "/[^/]+/managedZones" ),
140
148
PROJECT_GET ("GET" , CONTEXT + "/[^/]+" ),
141
- RECORD_LIST ("GET" , CONTEXT + "/[^/]+/managedZones/[^/]+/rrsets" );
149
+ RECORD_LIST ("GET" , CONTEXT + "/[^/]+/managedZones/[^/]+/rrsets" ),
150
+ BATCH ("POST" , "/batch" );
142
151
143
152
private final String method ;
144
153
private final String pathRegex ;
@@ -273,13 +282,18 @@ private String toJson(String message) throws IOException {
273
282
private class RequestHandler implements HttpHandler {
274
283
275
284
private Response pickHandler (HttpExchange exchange , CallRegex regex ) {
276
- URI relative = BASE_CONTEXT .relativize (exchange .getRequestURI ());
285
+ URI relative = null ;
286
+ try {
287
+ relative = BASE_CONTEXT .relativize (new URI (exchange .getRequestURI ().getRawPath ()));
288
+ } catch (URISyntaxException e ) {
289
+ return Error .INTERNAL_ERROR .response ("Parsing URI failed." );
290
+ }
277
291
String path = relative .getPath ();
278
292
String [] tokens = path .split ("/" );
279
293
String projectId = tokens .length > 0 ? tokens [0 ] : null ;
280
294
String zoneName = tokens .length > 2 ? tokens [2 ] : null ;
281
295
String changeId = tokens .length > 4 ? tokens [4 ] : null ;
282
- String query = relative .getQuery ();
296
+ String query = exchange . getRequestURI () .getQuery ();
283
297
switch (regex ) {
284
298
case CHANGE_GET :
285
299
return getChange (projectId , zoneName , changeId , query );
@@ -307,6 +321,12 @@ private Response pickHandler(HttpExchange exchange, CallRegex regex) {
307
321
} catch (IOException ex ) {
308
322
return Error .BAD_REQUEST .response (ex .getMessage ());
309
323
}
324
+ case BATCH :
325
+ try {
326
+ return handleBatch (exchange );
327
+ } catch (IOException ex ) {
328
+ return Error .BAD_REQUEST .response (ex .getMessage ());
329
+ }
310
330
default :
311
331
return Error .INTERNAL_ERROR .response ("Operation without a handler." );
312
332
}
@@ -319,7 +339,11 @@ public void handle(HttpExchange exchange) throws IOException {
319
339
for (CallRegex regex : CallRegex .values ()) {
320
340
if (requestMethod .equals (regex .method ) && rawPath .matches (regex .pathRegex )) {
321
341
Response response = pickHandler (exchange , regex );
322
- writeResponse (exchange , response );
342
+ if (response != null ) {
343
+ /* null response is returned by batch request, because it handles writing
344
+ the response on its own */
345
+ writeResponse (exchange , response );
346
+ }
323
347
return ;
324
348
}
325
349
}
@@ -328,6 +352,67 @@ public void handle(HttpExchange exchange) throws IOException {
328
352
requestMethod , exchange .getRequestURI ())));
329
353
}
330
354
355
+ private Response handleBatch (final HttpExchange exchange ) throws IOException {
356
+ String contentType = exchange .getRequestHeaders ().getFirst ("Content-type" );
357
+ if (contentType != null ) {
358
+ int port = server .getAddress ().getPort ();
359
+ HttpMediaType httpMediaType = new HttpMediaType (contentType );
360
+ String boundary = httpMediaType .getParameter ("boundary" );
361
+ MultipartStream multipartStream =
362
+ new MultipartStream (exchange .getRequestBody (), boundary .getBytes (), 1024 , null );
363
+ ByteArrayOutputStream out = new ByteArrayOutputStream ();
364
+ byte [] bytes = new byte [1024 ];
365
+ multipartStream .skipPreamble ();
366
+ while (multipartStream .readBoundary ()) {
367
+ Socket socket = new Socket ("localhost" , port );
368
+ OutputStream socketOutput = socket .getOutputStream ();
369
+ ByteArrayOutputStream section = new ByteArrayOutputStream ();
370
+ multipartStream .readBodyData (section );
371
+ String line ;
372
+ String contentId = null ;
373
+ Scanner scanner = new Scanner (new String (section .toByteArray ()));
374
+ while (scanner .hasNextLine ()) {
375
+ line = scanner .nextLine ();
376
+ if (line .isEmpty ()) {
377
+ break ;
378
+ } else if (line .toLowerCase ().startsWith ("content-id" )) {
379
+ contentId = line .split (":" )[1 ].trim ();
380
+ }
381
+ }
382
+ String requestLine = scanner .nextLine ();
383
+ socketOutput .write ((requestLine + " \r \n " ).getBytes ());
384
+ socketOutput .write ("Connection: close \r \n " .getBytes ());
385
+ while (scanner .hasNextLine ()) {
386
+ line = scanner .nextLine ();
387
+ socketOutput .write (line .getBytes ());
388
+ if (!line .isEmpty ()) {
389
+ socketOutput .write (" \r \n " .getBytes ());
390
+ } else {
391
+ socketOutput .write ("\r \n " .getBytes ());
392
+ }
393
+ }
394
+ socketOutput .flush ();
395
+ InputStream in = socket .getInputStream ();
396
+ int length ;
397
+ out .write (RESPONSE_SEPARATOR .getBytes ());
398
+ out .write ("Content-Type: application/http \r \n " .getBytes ());
399
+ out .write (("Content-ID: " + contentId + " \r \n \r \n " ).getBytes ());
400
+ try {
401
+ while ((length = in .read (bytes )) != -1 ) {
402
+ out .write (bytes , 0 , length );
403
+ }
404
+ } catch (IOException ex ) {
405
+ // this handles connection reset error
406
+ }
407
+ }
408
+ out .write (RESPONSE_END .getBytes ());
409
+ writeBatchResponse (exchange , out );
410
+ } else {
411
+ return Error .BAD_REQUEST .response ("Content-type header was not provided for batch." );
412
+ }
413
+ return null ;
414
+ }
415
+
331
416
/**
332
417
* @throws IOException if the request cannot be parsed.
333
418
*/
@@ -368,7 +453,8 @@ private LocalDnsHelper(long delay) {
368
453
try {
369
454
server = HttpServer .create (new InetSocketAddress (0 ), 0 );
370
455
port = server .getAddress ().getPort ();
371
- server .createContext (CONTEXT , new RequestHandler ());
456
+ server .setExecutor (Executors .newCachedThreadPool ());
457
+ server .createContext ("/" , new RequestHandler ());
372
458
} catch (IOException e ) {
373
459
throw new RuntimeException ("Could not bind the mock DNS server." , e );
374
460
}
@@ -430,6 +516,20 @@ private static void writeResponse(HttpExchange exchange, Response response) {
430
516
}
431
517
}
432
518
519
+ private static void writeBatchResponse (HttpExchange exchange , ByteArrayOutputStream output ) {
520
+ exchange .getResponseHeaders ().set (
521
+ "Content-type" , "multipart/mixed; boundary=" + RESPONSE_BOUNDARY );
522
+ try {
523
+ exchange .getResponseHeaders ().add ("Connection" , "close" );
524
+ exchange .sendResponseHeaders (200 , output .toByteArray ().length );
525
+ OutputStream responseBody = exchange .getResponseBody ();
526
+ output .writeTo (responseBody );
527
+ responseBody .close ();
528
+ } catch (IOException e ) {
529
+ log .log (Level .WARNING , "IOException encountered when sending response." , e );
530
+ }
531
+ }
532
+
433
533
private static String decodeContent (Headers headers , InputStream inputStream ) throws IOException {
434
534
List <String > contentEncoding = headers .get ("Content-encoding" );
435
535
InputStream input = inputStream ;
0 commit comments