2015-07-12 4 views
0

Я занимаюсь созданием прокси-сервера на Java; сервер правильно обрабатывает HTTP-трафик, но не удается правильно туннелировать трафик HTTPS. Согласно IETF draft, выполняется следующий процесс:Java Proxy Tunneling HTTPS

1) Я успешно получаю сообщение CONNECT из браузера, содержащего HOST для подключения в виде обычного текста.

2) Я разобрал это сообщение, чтобы извлечь детали хоста и успешно установить соединение с удаленным хостом.

3) Затем я отправляю сообщение об установлении соединения HTTP/1.0 200 обратно клиенту, а затем сразу же пытаюсь передать трафик с обеих сторон соединения, используя приведенный ниже код.

Проблема заключается в том, что после того, как я верну сообщение выше 200 вместо отправки данных HTTPS, браузер, кажется, входит в бесконечный цикл и продолжает отправлять дальнейшие сообщения CONNECT в прокси. Вот код, я использую для передачи данных между клиентом и хостом:

public static void stageTunnelledConnection(Socket clientSocket,Socket targetHostSocket) throws IOException{ 

    //set client socket read timeout to 2 seconds. The targethost connection will ALREADY have been 
    //set to this value at the time this method is called. 
    clientSocket.setSoTimeout(2000); 

    InputStream[] socketInputStreamsArr = new InputStream[]{clientSocket.getInputStream(),targetHostSocket.getInputStream()}; 

    OutputStream[] socketOutputStreamsArr = new OutputStream[]{clientSocket.getOutputStream(),targetHostSocket.getOutputStream()}; 

    //holds current socket index to read from, this will be switched between the two sockets 
    //at 0 and 1 indexes of the sockets array respectively. 
    int curReadIndex = 0; 
    //this will be set according to the "curReadIndex" value and will typically be 
    //the logical NOT of that value; that is, where curReadIndex equals 0 the curWriteIndex to will equal 1 and visa versa. 
    int curWriteIndex = 1; 
    while(true){ 

     try{ 

     //attempt to read from socket stream at current index and write 
     //to the socket at the alternate index. 
     byte[] dataBuff = new byte[2048]; 

     int bytesRead = 0; 
     //we read into the dataBuff this operation will block for 
     //a max of 2 seconds should no data be available to read 
     while((bytesRead = socketInputStreamsArr[curReadIndex].read(dataBuff)) != -1){ 

      //ByteArrayInputStream bais = new ByteArrayInputStream(dataBuff); 

      //BufferedReader br = new BufferedReader(new InputStreamReader(bais)); 

      //System.out.println(br.readLine()); 

      //write the buffer to the outputsteam at the index 
      //computed and stored to the "curWriteIndex" var above. 
      socketOutputStreamsArr[curWriteIndex].write(dataBuff); 
      socketOutputStreamsArr[curWriteIndex].flush(); 

      //System.out.println("Bytes read=".concat(String.valueOf(dataBuff))); 
      //System.out.println("wroteBytes: "+bytesRead); 

     } 



     } 

     catch(SocketTimeoutException ste){ 

     //we switch read/write index each time a read timeout error occurs. I.e 
     //were there is no further data to read from the socket at the currrent read index. 
     if(ste.getMessage().contains("Read")){ 
     //System.out.println("Switching connection."); 
     curReadIndex = (curReadIndex == 0) ? 1 : 0; 
     curWriteIndex = (curReadIndex == 0) ? 1 : 0; 
     } 
     else{ 

      //clientSocket.close(); 
      //targetHostSocket.close(); 
      ste.printStackTrace(); 
     } 

     } 
     catch(SocketException ioe){ 

      //if an input/output exception occurs we must close both sockets 
      clientSocket.close(); 
      targetHostSocket.close(); 

      ioe.printStackTrace(); 

     } 



    } 


} 

** ВАЖНО: ** Как фактические данные, туннелировано шифруется и, таким образом, непрозрачными для прокси, прокси-сервер должен быть готовый читать/писать с любой стороны в любое время. Чтобы облегчить этот процесс в одном потоке, я устанавливаю относительно короткий тайм-аут Socket Timeout (2 секунды) с обеих сторон и вхожу в цикл, который чередует, с какой стороны он считывает и записывает на каждую итерацию, где нет данных, SocketTimeoutException происходит, что поймано, сторона, на которую следует читать, переключается в этой точке, и цикл продолжает выполняться. Может ли эта стратегия, направленная на чтение из двух сокетов в одном потоке, вызвать проблему?

+0

Вы уверены, что рукопожатие с CONNECT и ответ (который вы не показываете) верны? Если это неверно, это может объяснить, что браузер снова пытается создать новый CONNECT. Вы можете добавить пакетный захват соединения между клиентом и прокси, чтобы показать, что происходит на самом деле (используйте cloudshark.org). –

ответ

1
socketOutputStreamsArr[curWriteIndex].write(dataBuff); 

Это должно быть

socketOutputStreamsArr[curWriteIndex].write(dataBuff, 0, bytesRead); 

Для того чтобы облегчить этот процесс в одном потоке я установил относительно короткий сокет тайм-аута (2 секунды) на обеих сторонах и ввести в петлю, которая чередуется с какой стороны он считывает и записывает на каждую итерацию, где нет данных, возникает событие SocketTimeoutException, которое поймано, сторона, с которой выполняется чтение, переключается в этой точке, и цикл продолжает выполняться. Может ли эта стратегия, направленная на чтение из двух сокетов в одном потоке, вызвать проблему?

Да. Вы должны использовать либо два потока, либо неблокирующий NIO. В противном случае вы просто добавляете много нежелательной задержки.

+0

Я думаю, что метод: write (dataBuff) эквивалентен: write (dataBuff, 0, dataBuff.length) Я использую первый для простоты. –

+0

Если, конечно, OutputStream не будет писать нулевые байты, где dataBuff содержит меньше 2048 байт. Я изменил код, чтобы использовать перегруженный метод записи, как вы описали, и отчитайтесь здесь. –

+0

Haha It WORKS !!! Спасибо Вам большое!! кто бы мог подумать, что такое простое изменение (т. е. использование метода overloaded write() было бы всем, что требовалось). Оказывается, моя стратегия чтения из двух сокетов с одним потоком отлично работает. Как я уже ранее указывал, ошибка должна происходить из-за буфера ENTIRE, записываемого в выходной поток, даже когда он содержит МЕНЬШЕ, что заданные 2048 байтов.Я бы подумал, что реализация выходного потока была бы достаточно интеллектуальной, чтобы опускать нулевые индексы в предоставленном массиве. –