簡體   English   中英

HTTP隧道Servlet(Java)

[英]HTTP Tunnel Servlet (Java)

我正在嘗試編寫HTTP隧道,因為我們希望能夠通過我們的Web應用程序連接到遠程計算機。 雖然我知道所涉及的安全風險,但這是我們想要做的事情。 它不是在互聯網上托管,而是在私人網絡上托管,因此風險被認為很低。

基本要求是允許Java調試工具通過servlet連接到計算機。 我們有一些客戶堅持讓開發箱位於防火牆的一邊,而且由於java調試服務器上的返回端口沒有修復,我們不能簡單地要求他們打開一個特定的端口。

代碼還不完美。 我只是試圖以雙向方式進行溝通。

有幾個組件。 Eclipse中java調試連接的獨立服務器。 此服務器配置為根據連接的端口知道它的標題。 因此,如果端口1166被命中,它就知道連接到機器x上的servlet。

即Eclipse Debugger - > Debug Proxy Server - > Application Servlet - > Application JVM

到目前為止,我的努力,我似乎​​能夠連接,但流不是完全功能。 Eclipse向JVM發送一個JDWP-Handshake,它應該用JDWP-Handshake回復。 我發現當Eclipse發送JDWP-Handshake時,它被寫入調試代理服務器,然后轉發到Servlet,但是它似乎在servlet中被忽略了。 我收到的日志如下:

[INFO] Started Jetty Server
2012-06-18 10:00:53,356  INFO ProxySocket  - Connection received, forwarding to tidevwls03:1166 via http://localhost:8080/tunnel/debug-proxy
2012-06-18 10:00:53,361  INFO ProxySocket  - Connected to http://localhost:8080/tunnel/debug-proxy
2012-06-18 10:00:53,603  INFO ProxyServlet  - Received incoming http connection, attempting to forward to endpoint tidevwls03:1166
2012-06-18 10:00:53,604  INFO ProxyServlet  - Connecting to endpoint tidevwls03:1166
2012-06-18 10:00:53,613  INFO StreamProxy  - [endpoint-read -> http-write    ] beginning proxy transport.
2012-06-18 10:00:53,613  INFO StreamProxy  - [http-read     -> endpoint-write] beginning proxy transport.
2012-06-18 10:00:53,619  INFO ProxySocket  - Response Header: HTTP/1.1 200 OK
2012-06-18 10:00:53,619  INFO ProxySocket  - Response Header: Content-Length: 0
2012-06-18 10:00:53,623  INFO ProxySocket  - Response Header: Server: Jetty(6.1.22)
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] beginning proxy transport.
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'J'
2012-06-18 10:00:53,624  INFO StreamProxy  - [servlet-read -> client-write   ] beginning proxy transport.
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'D'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'W'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'P'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] '-'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'H'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'a'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'n'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'd'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 's'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'h'
2012-06-18 10:00:53,624  INFO StreamProxy  - [client-read  -> servlet-write  ] 'a'
2012-06-18 10:00:53,625  INFO StreamProxy  - [client-read  -> servlet-write  ] 'k'
2012-06-18 10:00:53,625  INFO StreamProxy  - [client-read  -> servlet-write  ] 'e'

我想知道是否需要改變我對此的想法,以便將流分解為多個請求並使用基於會話的連接。 一個請求將成為永無止境的下游(即無限響應),然后當客戶端發送到servlet時,它將每次創建一個新請求。 這是讓這個工作的關鍵嗎?

下面是可以獨立運行的調試代理服務器的代碼,或者我已暫時將其配置為在Jetty服務器上作為servlet運行,以便快速測試周轉時間。 (ProxySocket.java)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.List;

import javax.net.SocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ProxySocket extends HttpServlet {
  private static final Logger logger = Logger.getLogger( ProxySocket.class ); 
  private static final ApplicationContext springContext = new ClassPathXmlApplicationContext( "env-spring/applicationContext*.xml" ); 

  @Override
  public void init() throws ServletException {
    List<HttpDebugConfig> configs = ( List<HttpDebugConfig> ) springContext.getBean( "DebugProxyHosts" );
    for ( HttpDebugConfig config : configs ) {
      ProxyServer proxyServer = new ProxyServer( config );
      proxyServer.start();
    }
  }

  class ProxyServer extends Thread {
    private HttpDebugConfig config;

    public ProxyServer( HttpDebugConfig config ) {
      this.config = config;
    }

    public void run() {
      ServerSocket ss = null;
      StreamProxy streamToTunnel = null;
      StreamProxy streamToClient = null;

      try {
        ss = new ServerSocket( config.getLocalPort() );
        Socket inbound = null;
        Socket outbound = null;
        logger.info( String.format( "Listening for connections on port %d. Proxying to %s:%d", config.getLocalPort(), config.getRemoteHost(), config.getRemotePort() ) );
        while ( ( inbound = ss.accept() ) != null ) {
          try {
            logger.info( String.format( "Connection received, forwarding to %s:%d via %s", config.getRemoteHost(), config.getRemotePort(), config.getProxyUrl() ) );

            URL proxy = new URL( config.getProxyUrl() );

            outbound = SocketFactory.getDefault().createSocket( proxy.getHost(), proxy.getPort() );
            logger.info( String.format( "Connected to %s", config.getProxyUrl() ) );

            OutputStream out = outbound.getOutputStream();
            BufferedReader in = new BufferedReader( new InputStreamReader( outbound.getInputStream() ) );

            writeLine( out, String.format( "POST %s HTTP/1.1", config.getProxyUrl() ) );
            writeLine( out, String.format( "Host: http://%s:%s", proxy.getHost(), proxy.getPort() ) );
            writeLine( out, "Connection: keep-alive" );
            writeLine( out, String.format( "tunnel_host: %s", config.getRemoteHost() ) );
            writeLine( out, String.format( "tunnel_port: %s", String.valueOf( config.getRemotePort() ) ) );
            writeLine( out, "" );

            // read the http response and then we can start tunnelling.
            for ( String line = ""; StringUtils.isNotBlank( line = in.readLine() ); ) {
              logger.info( String.format( "Response Header: %s", line ) );
            }

            streamToTunnel = new StreamProxy( "[client-read  -> servlet-write  ]", inbound.getInputStream(), outbound.getOutputStream() );
            streamToClient = new StreamProxy( "[servlet-read -> client-write   ]", outbound.getInputStream(), inbound.getOutputStream() );
            streamToTunnel.start();
            streamToClient.start();

            while ( streamToClient.isAlive() || streamToTunnel.isAlive() ) {
              try { Thread.sleep( 100 ); } catch ( InterruptedException e ) { }
            }

            logger.info( String.format( "Shutting down socket-to-%s.", config.getProxyUrl() ) );
          } finally {
            IOUtils.closeQuietly( inbound );
            IOUtils.closeQuietly( outbound );
          }
        }
      } catch ( IOException e ) {
        logger.error( String.format( "No longer listening for connections on port %d. Proxying to %s:%d", config.getLocalPort(), config.getRemoteHost(), config.getRemotePort() ), e );
      } finally {
        if ( ss != null ) {
          try { ss.close(); } catch ( Exception e ) { }
        }
      }
    }

    private void writeLine( OutputStream out, String msg ) throws IOException {
      out.write( String.format( "%s\n", StringUtils.defaultString( msg ) ).getBytes() );
    }
  }
}

下一部分代碼是spring配置(/en-spring/applicationContext.xml)。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
    ">
    <util:list id="DebugProxyHosts" list-class="java.util.ArrayList">
        <bean class="HttpDebugConfig">
            <property name="localPort" value="1166" />
            <property name="proxyUrl" value="http://localhost:8080/tunnel/debug-proxy" />
            <property name="remoteHost" value="tidevwls03" />
            <property name="remotePort" value="1166" />
        </bean> 
    </util:list>
</beans>

配置bean(HttpDebugConfig.java)。

public class HttpDebugConfig {
  private int localPort;
  private String remoteHost;
  private int remotePort;
  private String proxyUrl;

  public int getLocalPort() {
    return localPort;
  }

  public void setLocalPort( int localPort ) {
    this.localPort = localPort;
  }

  public String getRemoteHost() {
    return remoteHost;
  }

  public void setRemoteHost( String remoteHost ) {
    this.remoteHost = remoteHost;
  }

  public int getRemotePort() {
    return remotePort;
  }

  public void setRemotePort( int remotePort ) {
    this.remotePort = remotePort;
  }

  public String getProxyUrl() {
    return proxyUrl;
  }

  public void setProxyUrl( String proxyUrl ) {
    this.proxyUrl = proxyUrl;
  }
}

輸出流復制器的輸入流(StreamProxy.java)

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

public class StreamProxy extends Thread {
  private static final Logger logger = Logger.getLogger( StreamProxy.class );

  private InputStream in;
  private OutputStream out;

  private boolean kill = false;

  public StreamProxy( String name, InputStream in, OutputStream out ) {
    this.in = in;
    this.out = out;
    setName( name );
  }

  @Override
  public void interrupt() {
    this.kill = true;
    super.interrupt();
  }

  @Override
  public void run() {
    try {
      logger.info( String.format( "%s beginning proxy transport.", getName() ) );
      do {
        int n = 0;
        while ( -1 != ( n = in.read() ) ) {
          logger.info( getName() + " '" + ( char ) n + "'" );
          out.write( n );
          // out.flush();
        }
        try { Thread.sleep( 1 ); } catch ( Exception e ) { }
      } while ( ! kill );
      logger.info( String.format( "%s completed proxy transport.", getName() ) );
    } catch ( IOException e ) {
      logger.error( String.format( "%s Failed to copy from input stream to output stream. Aborting thread.", getName() ),  e );
      kill = true;
    } finally {
      IOUtils.closeQuietly( in );
      IOUtils.closeQuietly( out );
    }
  }
}

這部分是隧道Servlet(ProxyServlet.java)

import java.io.IOException;
import java.net.Socket;

import javax.net.SocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;

public class ProxyServlet extends HttpServlet {
  private static final Logger logger = Logger.getLogger( ProxyServlet.class ); 
  private static final long serialVersionUID = -686421490573011755L;

  @Override
  protected void service( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
    new Runner( request, response ).start();
  }

  class Runner extends Thread {
    private HttpServletRequest request;
    private HttpServletResponse response;

    public Runner( HttpServletRequest request, HttpServletResponse response ) {
      this.request = request;
      this.response = response;
    }

    @Override
    public void run() {
      Socket endpoint = null;
      StreamProxy streamToHttp = null;
      StreamProxy streamToEndpoint = null;

      String host = StringUtils.defaultIfEmpty( request.getHeader( "tunnel_host" ), "localhost" );
      int port = NumberUtils.toInt( request.getHeader( "tunnel_port" ), 8000 );

      try {
        logger.info( String.format( "Received incoming http connection, attempting to forward to endpoint %s:%d", host, port ) );

        logger.info( String.format( "Connecting to endpoint %s:%d", host, port ) );
        endpoint = SocketFactory.getDefault().createSocket( host, port );

        streamToHttp = new StreamProxy( "[endpoint-read -> http-write    ]", endpoint.getInputStream(), response.getOutputStream() );
        streamToEndpoint = new StreamProxy( "[http-read     -> endpoint-write]", request.getInputStream(), endpoint.getOutputStream() );
        streamToHttp.start();
        streamToEndpoint.start();

        while ( streamToEndpoint.isAlive() || streamToHttp.isAlive() ) {
          try { Thread.sleep( 100 ); } catch ( InterruptedException e ) { }
        }

        logger.info( String.format( "Safely shut down servlet-to-%s:%d proxy.", host, port ) );
      } catch ( IOException e ) {
        logger.error( String.format( "Shutting down servlet-to-%s:%d proxy.", host, port ), e );
      } finally {
        if ( streamToHttp != null ) {
          streamToHttp.interrupt();
        }
        if ( streamToEndpoint != null ) {
          streamToEndpoint.interrupt();
        }
        IOUtils.closeQuietly( endpoint );
      }
    }
  }
}

應用程序容器配置(web.xml)

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <display-name>tunnel</display-name>

    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:log4j.properties
        </param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:env-spring/applicationContext*.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>Debug Proxy</servlet-name>
        <servlet-class>ProxyServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Debug Proxy</servlet-name>
        <url-pattern>/debug-proxy</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>Debug Socket</servlet-name>
        <servlet-class>ProxySocket</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Debug Socket</servlet-name>
        <url-pattern>/debug-socket</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

最后,我正在用maven構建我的pom.xml。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>tunnel</groupId>
  <artifactId>tunnel</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

    <properties>
        <version.spring>3.1.1.RELEASE</version.spring>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.22</version>
                <configuration>
                    <webApp>${project.build.directory}/${project.build.finalName}.${project.packaging}</webApp>
                    <stopPort>9966</stopPort>
                    <stopKey>foo</stopKey>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>

        <!-- Utilities -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
            <version>3.0.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>junit</artifactId>
                    <groupId>junit</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>

        <!-- Spring Framework -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${version.spring}</version>
        </dependency>
    <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${version.spring}</version>
        </dependency>
    <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${version.spring}</version>
        </dependency>
    <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${version.spring}</version>
        </dependency>
    </dependencies> 
</project>

我使用以下Maven目標運行Jetty服務器

jetty:stop clean install jetty:run-war

希望你發現這個小項目很有趣! 我期待聽到您的想法和意見。

謝謝,斯圖爾特

SSH隧道 - 我的選擇http://en.wikipedia.org/wiki/Tunneling_protocol

ssh -L [bind_address:]port:sshserverhostname:targetmachinehostname port

-L指定將本地(客戶端)主機上的給定端口轉發到遠程端的給定主機和端口。 這通過分配套接字來偵聽本地端口,可選地綁定到指定的bind_address。 只要與此端口建立連接,就會通過安全通道轉發連接,並從遠程計算機連接到主機端口hostport。 端口轉發也可以在配置文件中指定。 可以通過將地址括在方括號中來指定IPv6地址。 只有超級用戶才能轉發特權端口。 默認情況下,本地端口根據GatewayPorts設置進行綁定。 但是,可以使用顯式bind_address將連接綁定到特定地址。 `localhost'' indicates that the listen- ing port be bound for local use only, while an empty address or的bind_address `localhost'' indicates that the listen- ing port be bound for local use only, while an empty address or *'表示該端口應該可從所有接口使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM