简体   繁体   English

HTTP隧道Servlet(Java)

[英]HTTP Tunnel Servlet (Java)

I am trying to write an HTTP tunnel as we want to be able to connect to a remote machine through our web application. 我正在尝试编写HTTP隧道,因为我们希望能够通过我们的Web应用程序连接到远程计算机。 While I am aware of the security risks involved, it's something we would like to do. 虽然我知道所涉及的安全风险,但这是我们想要做的事情。 It's not hosted on the internet, but on private networks, so the risk is considered low. 它不是在互联网上托管,而是在私人网络上托管,因此风险被认为很低。

The basic requirement is to allow the Java Debugging tool to connect through a servlet to a machine. 基本要求是允许Java调试工具通过servlet连接到计算机。 We have some clients that insist on having development boxes their side of the firewall and as the return port on the java debug server is not fixed, we can't simply ask them to open up a specific port. 我们有一些客户坚持让开发箱位于防火墙的一边,而且由于java调试服务器上的返回端口没有修复,我们不能简单地要求他们打开一个特定的端口。

The code isn't perfect yet. 代码还不完美。 I have simply been trying to get something communicating in a bi-directional manner. 我只是试图以双向方式进行沟通。

There are a few components. 有几个组件。 A standalone server which the java debug in Eclipse connects to. Eclipse中java调试连接的独立服务器。 This server is configured to know where it's heading based on the port connected to. 此服务器配置为根据连接的端口知道它的标题。 So if port 1166 is hit, it knows to connect to a servlet on machine x. 因此,如果端口1166被命中,它就知道连接到机器x上的servlet。

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

So far for my efforts, I appear to be able to connect, but the streams are not fully functional. 到目前为止,我的努力,我似乎​​能够连接,但流不是完全功能。 Eclipse sends a JDWP-Handshake to the JVM, which is supposed to reply with JDWP-Handshake back. Eclipse向JVM发送一个JDWP-Handshake,它应该用JDWP-Handshake回复。 I'm finding that when JDWP-Handshake is sent by Eclipse, it is written to the Debug Proxy Server and then relayed onto the Servlet, but it appears this is then being ignored in the servlet. 我发现当Eclipse发送JDWP-Handshake时,它被写入调试代理服务器,然后转发到Servlet,但是它似乎在servlet中被忽略了。 The logs I am receiving are the following: 我收到的日志如下:

[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'

I'm wondering if I need to change my thinking on this so that the streams are broken up into multiple requests and a session based connection is used. 我想知道是否需要改变我对此的想法,以便将流分解为多个请求并使用基于会话的连接。 One request would become a never ending downstream (ie infinite response), then when the client sends to the servlet, it would create a new request each time. 一个请求将成为永无止境的下游(即无限响应),然后当客户端发送到servlet时,它将每次创建一个新请求。 Is this the key to getting this working? 这是让这个工作的关键吗?

Below is the code for the Debug Proxy Server that can either run standalone or I have temporarily configured it to run as a servlet on a Jetty server for quick testing turn around time. 下面是可以独立运行的调试代理服务器的代码,或者我已暂时将其配置为在Jetty服务器上作为servlet运行,以便快速测试周转时间。 (ProxySocket.java) (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() );
    }
  }
}

The next section of code is the spring configuration (/env-spring/applicationContext.xml). 下一部分代码是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>

The configuration bean (HttpDebugConfig.java). 配置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;
  }
}

The input stream to output stream copier (StreamProxy.java) 输出流复制器的输入流(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 );
    }
  }
}

This section is the Tunnel Servlet (ProxyServlet.java) 这部分是隧道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 );
      }
    }
  }
}

The application container configuration (web.xml) 应用程序容器配置(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>

Finally, my pom.xml as I am building with maven. 最后,我正在用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>

I run Jetty server with the following Maven targets 我使用以下Maven目标运行Jetty服务器

jetty:stop clean install jetty:run-war

Hope you find this little project interesting! 希望你发现这个小项目很有趣! I look forward to hearing your ideas and comments. 我期待听到您的想法和意见。

Thanks, Stuart 谢谢,斯图尔特

SSH tunnel - my choice http://en.wikipedia.org/wiki/Tunneling_protocol SSH隧道 - 我的选择http://en.wikipedia.org/wiki/Tunneling_protocol

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

-L Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side. -L指定将本地(客户端)主机上的给定端口转发到远程端的给定主机和端口。 This works by allocating a socket to listen to port on the local side, optionally bound to the specified bind_address. 这通过分配套接字来侦听本地端口,可选地绑定到指定的bind_address。 Whenever a connection is made to this port, the connection is forwarded over the secure channel, and a connection is made to host port hostport from the remote machine. 只要与此端口建立连接,就会通过安全通道转发连接,并从远程计算机连接到主机端口hostport。 Port forwardings can also be specified in the configu- ration file. 端口转发也可以在配置文件中指定。 IPv6 addresses can be specified by enclosing the address in square brackets. 可以通过将地址括在方括号中来指定IPv6地址。 Only the superuser can forward privileged ports. 只有超级用户才能转发特权端口。 By default, the local port is bound in accor- dance with the GatewayPorts setting. 默认情况下,本地端口根据GatewayPorts设置进行绑定。 However, an explicit bind_address may be used to bind the connection to a specific address. 但是,可以使用显式bind_address将连接绑定到特定地址。 The bind_address of `localhost'' indicates that the listen- ing port be bound for local use only, while an empty address or *' indicates that the port should be available from all interfaces. `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