java netty开发一个http/https代理
生活随笔
收集整理的這篇文章主要介紹了
java netty开发一个http/https代理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
http代理數據傳播路徑:
https消息傳播模式:
實現方式:
編程語言:java 框架選擇:netty首先創建一個標準的netty啟動
EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000).childHandler(new ProxyServiceInit());ChannelFuture f = b.bind(PropertiesUtil.getIntProp("start.port")).sync();f.channel().closeFuture().sync();} finally {workGroup.shutdownGracefully();bossGroup.shutdownGracefully();}向其中添加這么兩個handler,其中HttpServerCodec是netty自帶的,HttpService是自己實現的
@Overrideprotected void initChannel(Channel channel) throws Exception {ChannelPipeline p = channel.pipeline();p.addLast("httpcode", new HttpServerCodec());p.addLast("httpservice", new HttpService());}HttpServerCodec會將客戶端傳進來的消息轉成httpobject對象,并且是已經被聚合了的http消息,我們在自己寫的HttpService中使用
自定義httpservice 集成simpleinbondhandlerpublic class HttpService extends SimpleChannelInboundHandler<HttpObject> {//保留全局ctxprivate ChannelHandlerContext ctx;//創建一會用于連接web服務器的 Bootstrap private Bootstrap b = new Bootstrap();//channelActive方法中將ctx保留為全局變量@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);this.ctx = ctx;}//Complete方法中刷新數據@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {if (msg instanceof HttpRequest) {//轉成 HttpRequestHttpRequest req = (HttpRequest) msg;if (PasswordChecker.digestLogin(req)) { //檢測密碼,后面講HttpMethod method = req.method(); //獲取請求方式,http的有get post ..., https的是 CONNECTString headerHost = req.headers().get("Host"); //獲取請求頭中的Host字段String host = "";int port = 80; //端口默認80String[] split = headerHost.split(":"); //可能有請求是 host:port的情況,host = split[0]; if (split.length > 1) { port = Integer.valueOf(split[1]);}Promise<Channel> promise = createPromise(host, port); //根據host和port創建連接到服務器的連接/*根據是http還是http的不同,為promise添加不同的監聽器*/if (method.equals(HttpMethod.CONNECT)) {//如果是https的連接promise.addListener(new FutureListener<Channel>() {@Overridepublic void operationComplete(Future<Channel> channelFuture) throws Exception {//首先向瀏覽器發送一個200的響應,證明已經連接成功了,可以發送數據了FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));//向瀏覽器發送同意連接的響應,并在發送完成后移除httpcode和httpservice兩個handlerctx.writeAndFlush(resp).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {ChannelPipeline p = ctx.pipeline();p.remove("httpcode");p.remove("httpservice");}});ChannelPipeline p = ctx.pipeline();//將客戶端channel添加到轉換數據的channel,(這個NoneHandler是自己寫的)p.addLast(new NoneHandler(channelFuture.getNow()));}});} else {//如果是http連接,首先將接受的請求轉換成原始字節數據EmbeddedChannel em = new EmbeddedChannel(new HttpRequestEncoder());em.writeOutbound(req);final Object o = em.readOutbound();em.close();promise.addListener(new FutureListener<Channel>() {@Overridepublic void operationComplete(Future<Channel> channelFuture) throws Exception {//移除 httpcode httpservice 并添加 NoneHandler,并向服務器發送請求的byte數據 ChannelPipeline p = ctx.pipeline();p.remove("httpcode");p.remove("httpservice");//添加handlerp.addLast(new NoneHandler(channelFuture.getNow()));channelFuture.get().writeAndFlush(o);}});}} else {ctx.writeAndFlush(PasswordChecker.getDigest());}} else {ReferenceCountUtil.release(msg);}}//根據host和端口,創建一個連接web的連接private Promise<Channel> createPromise(String host, int port) {final Promise<Channel> promise = ctx.executor().newPromise();b.group(ctx.channel().eventLoop()).channel(NioSocketChannel.class).remoteAddress(host, port).handler(new NoneHandler(ctx.channel())).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000).connect().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {if (channelFuture.isSuccess()) {promise.setSuccess(channelFuture.channel());} else {ctx.close();channelFuture.cancel(true);}}});return promise;}}noneHandler里面只做數據的轉發,沒有任何邏輯
public class NoneHandler extends ChannelInboundHandlerAdapter {private Channel outChannel;public NoneHandler(Channel outChannel) {this.outChannel = outChannel;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//System.out.println("交換數據");outChannel.write(msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {outChannel.flush();} }上面我們添加了密碼的驗證,去掉密碼驗證部分就能直接使用了,可以先去掉測試一下,添加密碼驗證放在服務器上更安全
密碼驗證的邏輯就是,
http(s)請求頭中包含我們約定好的密碼,如果沒有我們就發送一個407響應,這樣瀏覽器(chromr,firefox,ie)就知道代理服務器要驗證密碼,就會彈出窗口要我們輸入密碼,
如果驗證成功過一次,下次的請求瀏覽器會自動帶上密碼,不用再重新輸入
密碼的方式有兩種,一種是簡單的Basic,密碼是明文傳輸的,一種是digest方式
方式一 basic 方式
如果沒有輸入密碼的情況下,代理返回的響應是FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED);resp.headers().add("Proxy-Authenticate", "Basic realm=\"Text\"");resp.headers().setInt("Content-Length", resp.content().readableBytes());return resp;發送一個 PROXY_AUTHENTICATION_REQUIRED響應,響應頭不包含 "Basic realm=\"Text\"",其中 `Text`可以隨便起,這樣瀏覽器就能彈窗出來報文可能長這樣HTTP/1.0 407 PROXY_AUTHENTICATION_REQUIREDServer: SokEvo/1.0WWW-Authenticate: Basic realm="Text"Content-Type: text/htmlContent-Length: xxx 代理如何驗證密碼呢?就是將頭部的帳號密碼解析出來,和我們的帳號密碼進行比對 用戶輸入帳號密碼后,服務器可能收到這樣的報文Get /index.html HTTP/1.0Host:www.google.comProxy-Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx //basic方式登錄public static boolean basicLogin(HttpRequest req) {//獲取請求頭中的 Proxy-AuthorizationString s = req.headers().get("Proxy-Authorization");if (s == null) {return false;}//密碼的形式是 `Basic 帳號:密碼`用冒號拼接在一起,在取base64try {String[] split = s.split(" ");byte[] decode = Base64.decodeBase64(split[1]); //去數組中的第二個,第一個是一個Basic固定的字符String userNamePassWord = new String(decode);String[] split1 = userNamePassWord.split(":", 2);PasswordChecker.basicCheck(split1[0], split1[1]); //比較帳號密碼是不是我們自己的帳號密碼} catch (Exception e) {e.printStackTrace();return false;}return true;}方式二 digest方式,
這種方式是,服務器發送一個隨機數給瀏覽器,瀏覽器用密碼和一堆東西混合(用戶名,uri之類的)在一起,進行md5加密,將混合的東西傳給服務器, 那么服務器以同樣的方式進行操作一遍,如果得到的結果與瀏覽器傳上來的相同,那么就證明密碼正確,傳輸階段,不會暴露帳號密碼這個參考維基百科的 http digest 摘要加密方式頁面ok一個帶帳號密碼控制的http代理服務器開發完成
windows這樣配置就可以用proxy上網了,也可以放在服務器上跑
總結
以上是生活随笔為你收集整理的java netty开发一个http/https代理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021年中国民爆行业企业经营情况及主要
- 下一篇: libminimsgbus集成消息通讯库