2020import static java .net .HttpURLConnection .HTTP_NO_CONTENT ;
2121import static java .net .HttpURLConnection .HTTP_OK ;
2222
23+ import com .google .api .client .http .HttpMediaType ;
2324import com .google .api .client .json .JsonFactory ;
2425import com .google .api .client .json .jackson .JacksonFactory ;
2526import com .google .api .services .dns .model .Change ;
4344import com .sun .net .httpserver .HttpHandler ;
4445import com .sun .net .httpserver .HttpServer ;
4546
47+ import org .apache .commons .fileupload .MultipartStream ;
4648import org .joda .time .format .ISODateTimeFormat ;
4749
50+ import java .io .ByteArrayOutputStream ;
4851import java .io .IOException ;
4952import java .io .InputStream ;
5053import java .io .OutputStream ;
5154import java .math .BigInteger ;
5255import java .net .InetSocketAddress ;
56+ import java .net .Socket ;
5357import java .net .URI ;
5458import java .net .URISyntaxException ;
5559import java .nio .charset .StandardCharsets ;
6266import java .util .NavigableMap ;
6367import java .util .NavigableSet ;
6468import java .util .Random ;
69+ import java .util .Scanner ;
6570import java .util .Set ;
6671import java .util .SortedMap ;
6772import java .util .TreeMap ;
@@ -112,6 +117,9 @@ public class LocalDnsHelper {
112117 private static final ScheduledExecutorService EXECUTORS =
113118 Executors .newScheduledThreadPool (2 , Executors .defaultThreadFactory ());
114119 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 " ;
115123
116124 static {
117125 try {
@@ -138,7 +146,8 @@ private enum CallRegex {
138146 ZONE_GET ("GET" , CONTEXT + "/[^/]+/managedZones/[^/]+" ),
139147 ZONE_LIST ("GET" , CONTEXT + "/[^/]+/managedZones" ),
140148 PROJECT_GET ("GET" , CONTEXT + "/[^/]+" ),
141- RECORD_LIST ("GET" , CONTEXT + "/[^/]+/managedZones/[^/]+/rrsets" );
149+ RECORD_LIST ("GET" , CONTEXT + "/[^/]+/managedZones/[^/]+/rrsets" ),
150+ BATCH ("POST" , "/batch" );
142151
143152 private final String method ;
144153 private final String pathRegex ;
@@ -273,13 +282,18 @@ private String toJson(String message) throws IOException {
273282 private class RequestHandler implements HttpHandler {
274283
275284 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+ }
277291 String path = relative .getPath ();
278292 String [] tokens = path .split ("/" );
279293 String projectId = tokens .length > 0 ? tokens [0 ] : null ;
280294 String zoneName = tokens .length > 2 ? tokens [2 ] : null ;
281295 String changeId = tokens .length > 4 ? tokens [4 ] : null ;
282- String query = relative .getQuery ();
296+ String query = exchange . getRequestURI () .getQuery ();
283297 switch (regex ) {
284298 case CHANGE_GET :
285299 return getChange (projectId , zoneName , changeId , query );
@@ -307,6 +321,12 @@ private Response pickHandler(HttpExchange exchange, CallRegex regex) {
307321 } catch (IOException ex ) {
308322 return Error .BAD_REQUEST .response (ex .getMessage ());
309323 }
324+ case BATCH :
325+ try {
326+ return handleBatch (exchange );
327+ } catch (IOException ex ) {
328+ return Error .BAD_REQUEST .response (ex .getMessage ());
329+ }
310330 default :
311331 return Error .INTERNAL_ERROR .response ("Operation without a handler." );
312332 }
@@ -319,7 +339,11 @@ public void handle(HttpExchange exchange) throws IOException {
319339 for (CallRegex regex : CallRegex .values ()) {
320340 if (requestMethod .equals (regex .method ) && rawPath .matches (regex .pathRegex )) {
321341 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+ }
323347 return ;
324348 }
325349 }
@@ -328,6 +352,67 @@ public void handle(HttpExchange exchange) throws IOException {
328352 requestMethod , exchange .getRequestURI ())));
329353 }
330354
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+
331416 /**
332417 * @throws IOException if the request cannot be parsed.
333418 */
@@ -368,7 +453,8 @@ private LocalDnsHelper(long delay) {
368453 try {
369454 server = HttpServer .create (new InetSocketAddress (0 ), 0 );
370455 port = server .getAddress ().getPort ();
371- server .createContext (CONTEXT , new RequestHandler ());
456+ server .setExecutor (Executors .newCachedThreadPool ());
457+ server .createContext ("/" , new RequestHandler ());
372458 } catch (IOException e ) {
373459 throw new RuntimeException ("Could not bind the mock DNS server." , e );
374460 }
@@ -430,6 +516,20 @@ private static void writeResponse(HttpExchange exchange, Response response) {
430516 }
431517 }
432518
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+
433533 private static String decodeContent (Headers headers , InputStream inputStream ) throws IOException {
434534 List <String > contentEncoding = headers .get ("Content-encoding" );
435535 InputStream input = inputStream ;
0 commit comments