2015-12-08 9 views
6

A partire da this post ho cercato di implementare un piccolo server proxy che gestisce GET e POST richieste pure (basta sostituire la classe Handler da quella qui sotto):Java Proxy - non può scambiare dati dalla richiesta HTTP GET/POST correttamente

public static class Handler extends Thread { 

    public static final Pattern CONNECT_PATTERN 
      = Pattern.compile("CONNECT (.+):(.+) HTTP/(1\\.[01])", Pattern.CASE_INSENSITIVE); 
    public static final Pattern GET_POST_PATTERN 
      = Pattern.compile("(GET|POST) (?:http)://([^/:]*)(?::([^/]*))?(/.*) HTTP/(1\\.[01])", Pattern.CASE_INSENSITIVE); 

    private final Socket clientSocket; 
    private boolean previousWasR = false; 

    public Handler(Socket clientSocket) { 
     this.clientSocket = clientSocket; 
    } 

    @Override 
    public void run() { 
     try { 
      String request = readLine(clientSocket, Integer.MAX_VALUE); 

      Matcher connectMatcher = CONNECT_PATTERN.matcher(request); 
      Matcher getNpostMatcher = GET_POST_PATTERN.matcher(request); 

      System.out.println("Request: " +request); 
      if (connectMatcher.matches()) { 
       // ... 

      } else if (getNpostMatcher.matches()) { 
       String method = getNpostMatcher.group(1); 
       String hostString = getNpostMatcher.group(2); 
       String portString = getNpostMatcher.group(3); 
       String lengthString = null; 
       String line; 
       ArrayList<String> buffer = new ArrayList<String>(); 
       Integer port = portString == null || "".equals(portString) ? 80 : Integer.parseInt(portString); 
       Integer length = null; 

       buffer.add(request); 
       while ((line = readLine(clientSocket, Integer.MAX_VALUE)) != null) { 
        buffer.add(line); 

        if ("".equals(line)) break; 

        if (lengthString == null && line.startsWith("Content-Length: ")) { 
         lengthString = line.substring(16); 
         length = Integer.parseInt(lengthString); 
        } 
       } 

       try { 
        final Socket forwardSocket; 
        try { 
         forwardSocket = new Socket(hostString, port); 
         System.out.println(" " + forwardSocket); 

        } catch (IOException | NumberFormatException e) { 
         OutputStreamWriter outputStreamWriter 
           = new OutputStreamWriter(clientSocket.getOutputStream(), "ISO-8859-1"); 

         e.printStackTrace(); 
         outputStreamWriter.write("HTTP/" + connectMatcher.group(3) + " 502 Bad Gateway\r\n"); 
         outputStreamWriter.write("Proxy-agent: Simple/0.1\r\n"); 
         outputStreamWriter.write("\r\n"); 
         outputStreamWriter.flush(); 
         return; 
        } 

        PrintWriter printWriter = new PrintWriter(forwardSocket.getOutputStream()); 

        for (String bufferedLine : buffer) { 
         printWriter.println(bufferedLine); 
        } 
        printWriter.flush(); 

        if ("POST".equals(method) && length > 0) { 
         System.out.println ("Posting data ..."); 
         if (previousWasR) { // skip \n if existing 
          int read = clientSocket.getInputStream().read(); 
          if (read != '\n') { 
           forwardSocket.getOutputStream().write(read); 
          } 
          forwardData(threadId, clientSocket, forwardSocket, length, true); // only forward "Content-length" bytes 
         } else { 
          forwardData(threadId, clientSocket, forwardSocket, length, true); // only forward "Content-length" bytes 
         } 
        } 

        System.out.println ("Forwarding response ..."); 
        forwardData(threadId, forwardSocket, clientSocket, null, false); 

        if (forwardSocket != null) { 
         forwardSocket.close(); 
        } 

       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } finally { 
      try { 
       clientSocket.close(); 

      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    private static void forwardData(int threadId, Socket inputSocket, Socket outputSocket, Integer length, boolean isPost) { 
     try { 
      InputStream inputStream = inputSocket.getInputStream(); 
      try { 
       OutputStream outputStream = outputSocket.getOutputStream(); 
       try { 
        byte[] buffer = new byte[4096]; 
        int read; 
        if (length == null || length > 0) { 
         do { 
          if ((read = inputStream.read(buffer)) > 0) { 
           outputStream.write(buffer, 0, read); 
           if (inputStream.available() < 1) { 
            outputStream.flush(); 
           } 
           if (length != null) { 
            length = length - read; 
           } 
          } 
         } while (read >= 0 && (length == null || length > 0)); 
        } 
       } finally { 
        if (!outputSocket.isOutputShutdown()) { 
         if (!isPost) { 
          outputSocket.shutdownOutput(); 
         } 
        } 
       } 
      } finally { 
       if (!inputSocket.isInputShutdown()) { 
        inputSocket.shutdownInput(); 
       } 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    private String readLine(Socket socket, Integer noOfBytes) throws IOException { 
     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
     int next; 
     readerLoop: 
     while (noOfBytes-- > 0 && (next = socket.getInputStream().read()) != -1) { 
      if (previousWasR && next == '\n') { 
       previousWasR = false; 
       continue; 
      } 
      previousWasR = false; 
      switch (next) { 
       case '\r': 
        previousWasR = true; 
        break readerLoop; 
       case '\n': 
        break readerLoop; 
       default: 
        byteArrayOutputStream.write(next); 
        break; 
      } 
     } 
     return byteArrayOutputStream.toString("ISO-8859-1"); 
    } 
} 

Dopo aver riscontrato alcuni problemi con le richieste POST, ho capito che è importante arrestare i flussi nel modo giusto. Quindi finalmente il codice sopra funziona abbastanza bene quando si usa Internet Explorer.

Utilizzando altri browser, tuttavia, sembra che gli stream/socket non siano chiusi correttamente, perché a volte gli indicatori di caricamento sono in esecuzione da un po 'di tempo, anche se il contenuto sembra già essere caricato. A volte, i siti non sono completamente caricati e le discussioni sembrano appendere al ...

if ((read = inputStream.read(buffer)) > 0) { 

in forwardData(...). Non so come potrei scoprire, se lo stream potrebbe fornire alcuni dati o meno - o come evitare questa chiamata di blocco di read.

Qualcuno sa cosa sto facendo male e come posso inoltrare correttamente i dati, in modo che tutti i browser caricino i contenuti correttamente senza inutili ritardi?

risposta

1

Guarda, il problema è che stai usando forwardData() per copiare le risposte del server al client, perché non coinvolge il campo Content-Length:.

HTTP/1.1{HTTP/1.1 200 OK 
Cache-Control: no-cache, must-revalidate 
Pragma: no-cache 
Content-Type: text/html; Charset=utf-8 
Content-Encoding: gzip 
Expires: Fri, 01 Jan 1990 00:00:00 GMT 
Vary: Accept-Encoding 
Server: Microsoft-IIS/8.0 
Access-Control-Allow-Origin: * 
X-RADID: P301511016-T104210110-C24000000000248666 
P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo" 
Date: Tue, 15 Dec 2015 09:33:41 GMT 
Content-Length: 1656 

<1656 Bytes> 

Nell'esempio risposta sopra, il proxy deve arresto flussi dopo aver letto 1656 byte dal server. In caso contrario, leggere bloccherà.

Quindi quello che vi serve è un codice che cerca la risposta per il campo Content-Length:, per esempio:

private static void forwardData(int threadId, Socket inputSocket, Socket outputSocket, Integer length, boolean isPost) { 
    int cLength = -1; 
    int count = 0; 
    try { 
     InputStream inputStream = inputSocket.getInputStream(); 
     try { 
      OutputStream outputStream = outputSocket.getOutputStream(); 
      try { 
       byte[] buffer = new byte[4096]; 
       int read; 
       if (length == null || length > 0) { 
        do { 
         if ((read = inputStream.read(buffer)) > 0) { // search for "Content-Length: " 
          if (cLength == -1) { 
           String response = new String(buffer, "UTF-8"); 
           int pos = response.indexOf("Content-Length:"); 
           if (pos > 0) { 
            String lString = response.substring(pos + 16, pos + 24).replaceAll("([0-9]*).*\\n?\\r?.*", "$1"); 
            cLength = Integer.parseInt(lString); 
           } 
          } 

          if (cLength != -1) { // if length is given, count bytes from empty line on 
           if (count > 0) { // already started - so just add 
            count = count + read; 
           } else { // check if empty line exists, "\r\n\r\n" or "\r\r" 
            for (int n = 0; n < read; n++) { 
             if (buffer[n] == 13 && buffer[n + 1] == 13) { 
              count = read - (n + 2); // if so, set count to bytes read after the empty line 
             } 
             if (buffer[n] == 13 && buffer[n + 1] == 10 && buffer[n + 2] == 13 && buffer[n + 3] == 10) { 
              count = read - (n + 4); // same as above 
             } 
            } 
           } 
          } 

          outputStream.write(buffer, 0, read); 
          if (inputStream.available() < 1) { 
           outputStream.flush(); 
          } 
          if (length != null) { 
           length = length - read; 
          } 
         } 
        } while (read >= 0 && (length == null || length > 0) && (cLength == -1 || count < cLength)); 
       } 

      } finally { 
       if (!outputSocket.isOutputShutdown()) { 
        if (!isPost) { 
         outputSocket.shutdownOutput(); 
        } 
       } 
      } 
     } finally { 
      if (!inputSocket.isInputShutdown()) { 
       inputSocket.shutdownInput(); 
      } 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 

Anche se il codice di cui sopra non è al momento al 100% di lavoro, dovrebbe dare un'idea su come procedere. Se la lunghezza del contenuto è nullo e viene ricevuta una riga vuota, probabilmente è necessario arrestare anche i flussi.

+0

Questo mi ha portato decisamente un grande passo avanti. Grazie per l'aiuto! – Trinimon