我正在尝试编写一个 HTTP 隧道,因为我们希望能够通过我们的 Web 应用程序连接到远程计算机。虽然我知道其中涉及的安全风险,但这是我们愿意做的事情。它不托管在 Internet 上,而是托管在专用网络上,因此风险被认为很低。
基本要求是允许 Java 调试工具通过 servlet 连接到机器。我们有一些客户坚持让开发盒在他们的防火墙一侧,并且由于 java 调试服务器上的返回端口不固定,我们不能简单地要求他们打开一个特定的端口。
代码还不完善。我一直在尝试以双向方式进行通信。
有几个组件。 Eclipse 中的 java 调试连接到的独立服务器。该服务器配置为根据连接的端口知道它的前进方向。因此,如果端口 1166 被命中,它就知道连接到机器 x 上的一个 servlet。
即Eclipse 调试器 --> 调试代理服务器 --> 应用程序 Servlet --> 应用程序 JVM
到目前为止,就我的努力而言,我似乎能够连接,但数据流功能不全。 Eclipse 向 JVM 发送 JDWP-Handshake,JVM 应该用 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'
我想知道我是否需要改变我对此的想法,以便将流分解为多个请求并使用基于 session 的连接。一个请求将成为一个永无止境的下游(即无限响应),然后当客户端发送到 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配置(/env-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 );
}
}
}
这部分是Tunnel 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)到指定的 bind_address。每当连接到此
端口,连接通过安全通道转发,并与主机建立连接
来自远程机器的端口主机端口。端口转发也可以在配置中指定
口粮文件。可以通过将地址括在方括号中来指定 IPv6 地址。
只有 super 用户可以转发特权端口。默认情况下,本地端口绑定(bind)在accor-
与 GatewayPorts 设置共舞。但是,可以使用显式 bind_address 来绑定(bind)
连接到特定地址。 `localhost'' 的 bind_address 表示 listen-
ing 端口仅供本地使用,而空地址或 *' 表示该端口
应该可以从所有界面访问。
关于java - HTTP 隧道 Servlet (Java),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11079816/
是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg