如何劫持Java应用的HTTP请求
背景
全链路追踪中,针对部分特殊的流量,希望将它引导到特定服务上(这个特定服务不在正常请求的链路上)——问题可以被抽象为解决进程间通信过程中目标进程的选择。
进程间通信方式很多,本篇只关注Java进程间套接字通信下HTTP形式的请求劫持,引导特定流量到特定进程。
解决方案
可行的处理方案繁多。自顶向下从应用、框架、JVM、ContainerRuntime、SystemCall、网络协议栈等级别,均可着手解决。侵入性最强的操作就是要求所有业务应用都主动实现HTTP请求分流逻辑;次一级是提供二方库供业务应用主动集成;或者从系统层面进行改造,基于改写系统调用对请求进行劫持。
回顾两年前的所学,JVMTI为劫持HTTP请求提供了一个全新的解决思路。通过Agent改写应用启动时加载的类的字节码,劫持类的实例并完成目标功能。
由于Java项目间调用大量的使用了Apache的http-client库,改写变得相当简单。识别流量,并对特定流量改写请求的Host即可。
Demo
由于http-client对所有请求目标都统一由org.apache.http.HttpHost维护,控制变得极为简单。只需在HttpHost实例化时,改写类的构造方法,即完成了对目标的劫持工作(下例中强制将所有请求指向163.com)
publicclassAgentimplementsClassFileTransformer{ publicstaticvoidpremain(Stringargs,Instrumentationinstrumentation){ instrumentation.addTransformer(newAgent()); } @Override publicbyte[]transform(ClassLoaderloader,StringclassName,Class>classBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsIllegalClassFormatException{ if("org/apache/http/HttpHost".equals(className)){ ClassPoolpool=ClassPool.getDefault(); try{ CtClasshttpHost=pool.get("org.apache.http.HttpHost"); CtClassstring=pool.get("java.lang.String"); CtConstructorconstructor=httpHost.getDeclaredConstructor(newCtClass[]{string,CtClass.intType,string}); constructor.insertBefore("hostname=\"www.163.com\";"); byte[]bytes=httpHost.toBytecode(); FileOutputStreamfos=newFileOutputStream("/Users/fangfeng/a.class"); fos.write(bytes); returnbytes; }catch(NotFoundException|CannotCompileException|IOExceptione){ e.printStackTrace(); } } returnclassfileBuffer; } }
将整个项目打包为agent.jar的过程不做太多介绍,详见ffutop/http-client-plugin
针对需要劫持的项目,在启动参数中增加-javaagent:${PATH_TO}/http-client-plugin.jar
以上就是如何劫持Java应用的HTTP请求的详细内容,更多关于劫持Java应用的HTTP请求的资料请关注毛票票其它相关文章!