2017-09-11 2 views
3

Je veux écrire un programme simple en utilisant netty pour proxy demande http envoyer par le navigateur. Je pense qu'il peut être divisé en 3 étapesComment écrire un proxy http en utilisant netty

  1. get demande envoyer par navigateur
  2. envoyer au site
  3. recevoir des données de site Web et l'envoyer au navigateur.

Question:

  1. Comment traduire url dans hôte et le port quand je l'aide Bootstrap.connect (hôte, port);
  2. Lorsque j'utilise HttpServerResponseHandler.connect et ChannelHandlerContext.writeAndFlush (httpMessage); Pour envoyer des données au site Web, comment puis-je obtenir les données de réponse du site Web et les renvoyer au navigateur?

C'est ma première journée à étudier le netty, alors essayez de répondre aussi facilement que possible. Merci beaucoup.

public class Server { 
    public static void main(String[] args) throws InterruptedException { 
     final int port = 8888; 

     // copy from https://github.com/netty/netty/wiki/User-guide-for-4.x 
     EventLoopGroup bossGroup = new NioEventLoopGroup(); 
     EventLoopGroup workerGroup = new NioEventLoopGroup(); 
     try { 
      ServerBootstrap b = new ServerBootstrap(); 
      b.group(bossGroup, workerGroup) 
        .channel(NioServerSocketChannel.class) 
        .childHandler(new ChannelInitializer<SocketChannel>() { 
         @Override 
         public void initChannel(SocketChannel ch) throws Exception { 
          ch.pipeline().addLast(new HttpRequestDecoder(), new HttpServerRequestHandler()); 
         } 
        }) 
        .option(ChannelOption.SO_BACKLOG, 128) 
        .childOption(ChannelOption.SO_KEEPALIVE, true); 

      // Bind and start to accept incoming connections. 
      ChannelFuture f = b.bind(port).sync(); 

      // Wait until the server socket is closed. 
      // In this example, this does not happen, but you can do that to gracefully 
      // shut down your server. 
      f.channel().closeFuture().sync(); 
     } finally { 
      workerGroup.shutdownGracefully(); 
      bossGroup.shutdownGracefully(); 
     } 
    } 
} 
public class HttpServerRequestHandler extends ChannelInboundHandlerAdapter { 

    @Override 
    public void channelRead(ChannelHandlerContext ctx, Object msg) { 
     // step 1 get data from browser 
     if (msg instanceof LastHttpContent) { 
      ctx.close(); 
      return; 
     } 
     DefaultHttpRequest httpMessage = (DefaultHttpRequest) msg; 
     System.out.println("浏览器请求===================="); 
     System.out.println(msg); 
     System.out.println(); 
     doWork(ctx, httpMessage); 
    } 

    @Override 
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 
     cause.printStackTrace(); 
     ctx.close(); 
    } 

    private void doWork(ChannelHandlerContext ctx, final DefaultHttpRequest msg) { 
     // step 2 send data to website 
     // translate url into host and port 
     String host = msg.uri(); 
     int port = 80; 
     if (host.startsWith("https://")) { 
      host = host.replaceFirst("https://", ""); 
      port = 443; 
     } else if (host.startsWith("http://")) { 
      host = host.replaceFirst("http://", ""); 
      port = 80; 
     } 
     if (host.contains(":443")) { 
      host = host.replace(":443", ""); 
      port = 443; 
     } 

     EventLoopGroup workerGroup = new NioEventLoopGroup(); 
     try { 
      Bootstrap b = new Bootstrap(); 
      b.group(workerGroup); 
      b.channel(NioSocketChannel.class); 
      //b.option(ChannelOption.AUTO_READ, true); 
      b.handler(new ChannelInitializer<SocketChannel>() { 
       @Override 
       public void initChannel(SocketChannel ch) throws Exception { 
        ch.pipeline().addLast(new HttpServerResponseHandler(msg), new HttpRequestEncoder()); 
       } 
      }); 

      // question 1 
      ChannelFuture f = b.connect(host, port).sync(); 
      //ChannelFuture f = b.connect("www.baidu.com", 443).sync(); 
      f.channel().closeFuture().sync(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } finally { 
      workerGroup.shutdownGracefully(); 
     } 
    } 
} 
public class HttpServerResponseHandler extends ChannelOutboundHandlerAdapter { 

    private Object httpMessage; 

    public HttpServerResponseHandler(Object o) { 
     this.httpMessage = o; 
    } 


    @Override 
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { 
     System.out.println("网页请求结果========================="); 
     System.out.println(httpMessage); 
     System.out.println(); 
    } 

    @Override 
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4) 
     cause.printStackTrace(); 
     ctx.close(); 
    } 

    @Override 
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, 
         SocketAddress localAddress, ChannelPromise promise) throws Exception { 
     System.out.println("connect !!!!!!!!!!!"); 
     // question 2 
     ctx.writeAndFlush(httpMessage); 
    } 
} 
+0

Avez-vous pu le réparer? – AcidBurn

+0

@AcidBurn peine à le réparer. Vous pouvez voir la réponse souffler de l'aide. – Jet

Répondre

0

Coïncidence, j'ai aussi travaillé sur un serveur proxy Netty aux fins de l'apprentissage. J'ai un code entièrement fonctionnel que vous pouvez trouver sur mon GitHub, mais je vais répondre à vos questions ici. Netty a également un exemple de serveur proxy officiel here mais contrairement à mon code, ils n'ont pas de tests unitaires.

(FYI, Mon code est dans Kotlin).

Idée de base:

Lors de la création d'un serveur proxy, vous avez besoin d'un serveur pour accepter les demandes des clients, ainsi qu'un client de la télécommande que vous mandatement. Vous avez créé le serveur, mais pas le client. Il est préférable de réutiliser le EventLoop créé par le serveur plutôt que d'en créer un nouveau pour le client. Chaque boucle d'événement s'exécute sur un thread dédié, donc en créer plus générerait des threads supplémentaires, nécessitant un changement de contexte lors de l'échange de données entre le Channel et le client Channel.

Comment traduire url dans hôte et le port

Pour garder les choses simples, j'ai utilisé un HttpObjectAggregator qui agrège un HttpMessage et son suivant dans HttpContents un seul FullHttpRequest ou FullHttpResponse (selon si utilisé pour gérer les demandes ou les réponses). Définir l'URL est trivial: il suffit d'appeler FullHttpRequest.setUri.

Pour obtenir l'hôte et le port, appelez Channel.remoteAddress() sur le canal client et jeté résultant SocketAddress à un InetSocketAddress, à partir de laquelle vous pouvez obtenir l'hôte et le port. N'oubliez pas de réinitialiser de façon similaire l'en-tête Host si présent.

comment puis-je obtenir les données de réponse

Après avoir établi un canal client (que vous êtes absent), vous devez faire une demande sur ce canal. Le canal client a un gestionnaire avec une référence au canal du serveur d'origine. Une fois que le gestionnaire a reçu une réponse, il l'écrit dans le canal du serveur.

+0

Merci ... Je vais étudier votre code plus tard – Jet