2013-03-05 19 views
7

Stiamo utilizzando la classe interna HttpServer in un progetto per lo scambio di dati tra un client e un server su HTTP. Quando siamo passati a Java 7, abbiamo realizzato un ritardo nella consegna dei risultati. Potremmo ridurre il problema al seguente esempio:Ritardo 1s in HttpServer da Java 7

Classe EchoServer crea il contesto /echo che restituisce semplicemente la data corrente e l'URI della richiesta ad ogni richiesta. Questo servizio viene quindi richiamato da un client in un ciclo.

import java.io.IOException; 
import java.io.OutputStream; 
import java.net.InetSocketAddress; 
import java.util.Date; 

import com.sun.net.httpserver.HttpExchange; 
import com.sun.net.httpserver.HttpHandler; 
import com.sun.net.httpserver.HttpServer; 

public class EchoServer { 

    public static void main(String[] args) throws IOException { 
     HttpServer server = HttpServer.create(new InetSocketAddress(80), 0); 
     server.createContext("/echo", new EchoHandler()); 
     server.start(); 
    } 

    static class EchoHandler implements HttpHandler { 
     public void handle(HttpExchange httpExchange) throws IOException { 
      httpExchange.getResponseHeaders().add("Content-type", "text/html"); 
      String response = "<b>" + new Date() + "</b> for " + httpExchange.getRequestURI(); 
      httpExchange.sendResponseHeaders(200, response.length()); 
      OutputStream os = httpExchange.getResponseBody(); 
      os.write(response.getBytes()); 
      os.close(); 
     } 
    } 
} 

Il seguente client invoca il servizio in un ciclo infinito con classe URL e stampa il primo carattere del flusso restituito (che sarà il segno <). Inoltre, il cliente stampa l'ora corrente.

import java.io.BufferedReader; 
import java.io.InputStreamReader; 
import java.net.URL; 

public class EchoClient { 

    public static void main(String[] args) throws Exception{ 
     while(true) { 
      URL url = new URL("http://localhost:80/echo"); 

      BufferedReader rd = new BufferedReader(new InputStreamReader(url.openStream())); 
      int res = rd.read(); 
      System.out.println((char)res); 
      System.out.println(System.currentTimeMillis()); 
     } 
    } 
} 

Se questo codice viene eseguito su Java6, tutto funziona correttamente e un risultato viene stampato ca. ogni 5 ms.

% java -version 
java version "1.6.0_24" 
Java(TM) SE Runtime Environment (build 1.6.0_24-b07) 
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02, mixed mode) 

% java EchoClient 
< 
1362515635677 
< 
1362515635682 
< 
1362515635687 
< 
1362515635691 

Se il codice viene eseguito su Java7, ogni richiesta utilizza 1000 ms circa.

% java -version 
java version "1.7.0_17" 
Java(TM) SE Runtime Environment (build 1.7.0_17-b02) 
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode) 

% java EchoClient 
< 
1362517297845 
< 
1362517298844 
< 
1362517299845 
< 
1362517300845 

Sembra che un timeout di 1000 ms sia nascosto da qualche parte. Se il carattere viene letto su InputStreamReader anziché su BufferedReader, si verifica lo stesso ritardo. Se un byte viene letto direttamente dal flusso di input, non è possibile visualizzare alcun ritardo. D'altra parte, se il programma EchoClient è eseguito su un servlet, allora tutto funziona correttamente, indipendentemente dal fatto che sia utilizzato lo BufferedReader o lo InputStreamReader.

Sembra che la classe InputStreamReader si aspetti qualcosa dal server che non è più fornito dall'implementazione di Java 7 di HttpServer. Hai un'idea di cosa succede esattamente qui e di come questo problema possa essere risolto? Una soluzione? O questo è un bug?

Grazie!


ho aggiunto ulteriori tempi per il codice del client:

public static void main(String[] args) throws Exception{ 
    while(true) { 
     System.out.println("0: "+System.currentTimeMillis()); 
     URL url = new URL("http://localhost:80/echo"); 
     System.out.println("1: "+System.currentTimeMillis()); 
     InputStream in = url.openStream(); 
     System.out.println("2: "+System.currentTimeMillis()); 
     InputStreamReader isr = new InputStreamReader(in); 
     System.out.println("3: "+System.currentTimeMillis()); 
     char res = (char)isr.read(); // character read is `<` 
     System.out.println(res + ": "+System.currentTimeMillis()); 
    } 
} 

con il seguente risultato:

% java EchoClient 
0: 1362532555535 
1: 1362532555537 
2: 1362532555608 
3: 1362532555609 
<: 1362532555611 
0: 1362532555612 
1: 1362532555613 
2: 1362532556608 
3: 1362532556609 
<: 1362532556610 
0: 1362532556611 
1: 1362532556612 
2: 1362532557609 
3: 1362532557610 
<: 1362532557611 
0: 1362532557612 
1: 1362532557613 

la prima chiamata del openStream richiede un certo tempo (70ms), ma tutti ulteriori invocazioni di openStream richiedono molto più tempo (996ms circa).

+2

In realtà, com.sun.net.httpserver non fa parte dell'API Java, e Penso che non dovrebbe essere usato in progetti mission-critical. Puoi provare alternative come NanoHTTPD: http://elonen.iki.fi/code/nanohttpd/ – gd1

+0

Questo è molto interessante ma difficile da credere. Puoi inserire più debug di timestamp per trovare esattamente quale parte di codice impiega 1 sec? – irreputable

+1

è la chiamata a 'url.openStream()' che richiede 1000ms, ma solo alla sua seconda (e successiva) chiamata nel ciclo, e solo se successivamente viene generato un 'InputStreamReader' e viene usato per leggere un byte. Pertanto, 'BufferedReader' sembra innocente. – Dominik

risposta

0

Sembra che tu non stia chiudendo BufferedReader o InputStream restituito da url.openStream(). La mancata chiusura del flusso potrebbe causare problemi con la possibilità di riutilizzare la connessione nelle iterazioni successive (e il comportamento con bachi in generale).

Hai risultati diversi con chiamate esplicite a rd.close() e stream.close()?

+0

Chiamare 'rd.close() 'su BufferedReader non aiuta e neigher chiama' close' sul flusso di input restituito dall'URL. – Dominik

1

Appena archiviato un bug report con Oracle. Qui ho un ritardo di 38 ms per entrambe le versioni di Java (SE 6 o 7).

/** 
* @test 
* @bug 
* @summary pipelining delay on Ubuntu 12.04.01 LTS/amd64 
*/ 

import com.sun.net.httpserver.*; 

import java.util.*; 
import java.util.concurrent.*; 
import java.io.*; 
import java.net.*; 

public class Bug { 

    static int iterations = 20; 
    static long requiredMinimumDelay = 10L; 

    public static void main (String[] args) throws Exception { 
     Handler handler = new Handler(); 
     InetSocketAddress addr = new InetSocketAddress (0); 
     HttpServer server = HttpServer.create (addr, 0); 
     HttpContext ctx = server.createContext ("/test", handler); 
     ExecutorService executor = Executors.newCachedThreadPool(); 
     server.setExecutor (executor); 
     server.start(); 

     long minDelay = requiredMinimumDelay * 1000L; 

     try { 
      for(int i = 0; i < iterations; i++) { 
       URL url = new URL ("http://localhost:"+server.getAddress().getPort()+"/test/foo.html"); 
       HttpURLConnection urlc = (HttpURLConnection)url.openConnection(); 
       InputStream is = urlc.getInputStream(); 
       InputStreamReader isr = new InputStreamReader(is); 
       BufferedReader br = new BufferedReader(isr); 
       String res = br.readLine(); 
       br.close(); 

       // skip first few 
       if(i < iterations/2) { 
        continue; 
       } 

       long delay = System.currentTimeMillis() - Long.parseLong(res); 
       System.out.println("delay: "+delay+" ms"); 
       if(delay < minDelay) { 
        minDelay = delay; 
       } 
      } 
     } catch (Exception ex) {} 

     server.stop(2); 
     executor.shutdown(); 

     if(minDelay > requiredMinimumDelay) { 
      throw new Exception("minimum delay too large: "+minDelay); 
     } 
    } 

    static class Handler implements HttpHandler { 
     public void handle (HttpExchange t) 
      throws IOException 
     { 
      InputStream is = t.getRequestBody(); 
      Headers map = t.getRequestHeaders(); 
      Headers rmap = t.getResponseHeaders(); 
      while (is.read() != -1) ; 
      is.close(); 
      String response = Long.toString(System.currentTimeMillis())+"\n"; 
      t.sendResponseHeaders (200, response.length()); 
      OutputStream os = t.getResponseBody(); 
      os.write (response.getBytes()); 
      t.close(); 
     } 
    }  
} 

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8009548

Aggiornamento: scopre Oracle classificato come "due diversi bug" uno per i 38ms (che hanno giocato d'azzardo on?) E una per il 1000 ms uno, che hanno risolto qui:

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8014254

Quindi il 1000 ms è stato fissato con speranza nelle versioni "8b91" e "7u85" in base ai backport collegati.

0

Una soluzione (inizialmente da user1050755) è l'aggiunta di questo, prima che i sendResponseHeaders() metodo:

httpExchange.getResponseHeaders().add("Connection", "close"); 

che disabilita fondamentalmente "mantenere vivo" funzionalità, ma almeno per me, ha fatto andare da 1000ms a 50ms per richiesta, dal momento che non ho la possibilità di aggiornare il mio JRE facilmente. Anche se perde la funzionalità "keep alive" FWIW.

1

ho sperimentato lo stesso problema, ma il commento di user1050755 punti il ​​bug filled e ha una soluzione:

... questo non è un problema quando il server utilizza un pool di thread, ma per un singolo Server filettato, questo timeout dà luogo a un collo di bottiglia ..

Quindi, fare un server multi-threaded:

 final Executor multi = Executors.newFixedThreadPool(10); 
     final HttpServer server = HttpServer.create(new InetSocketAddress(s_HTTP_PORT), 5); 
     //... do your REST bindings here 
     server.setExecutor(multi); 
     server.start(); 

Ha lavorato come un fascino per me.

PS. commenti come "com.sun.net.httpserver è terribile" non dare alcun aiuto - è lo stesso di "usa Apache invece"