基于Netty的HTTP通信研究分析

来源:互联网 发布:信鸽推送 php代码 编辑:程序博客网 时间:2024/05/11 05:24
1.HTTP服务器的创建

 

服务器创建时的代码同TCP完全相同。

/**HTTP方式(TCP)*/

    static ChannelFactory HTTPCHANNEL_FACTORY = newNioServerSocketChannelFactory(Executors.newCachedThreadPool(),

            Executors.newCachedThreadPool());

    static ServerBootstrap HTTPSERVER_BOOTSTRAP = new ServerBootstrap(HTTPCHANNEL_FACTORY);

 

/**

     * HTTP服务器启动

     *

     * @author linfenliang

     * @date 2012-7-23

     * @version V1.0.0

     * void

     */

    public static void httpServerStartUp() {

        // TODO Auto-generated method stub

        HTTPSERVER_BOOTSTRAP.setPipelineFactory(new HTTPServerPipelineFactory());

        // 这个配置项仅适用于我们接收到的通道实例,而不是ServerSocketChannel实例

        HTTPSERVER_BOOTSTRAP.setOption("child.tcpNoDelay"true);

        HTTPSERVER_BOOTSTRAP.setOption("child.keepAlive"true);

        HTTPSERVER_BOOTSTRAP.setOption("reuseAddress"true);

        // 绑定这个服务使用的端口

        HTTPSERVER_BOOTSTRAP.bind(new InetSocketAddress(Constants.SERVER_NAME,Constants.HTTPSERVER_PORT));

        LOGGER.info("HTTP服务已启动....");

    }

    /**

     * HTTP服务器关闭

     *

     * @author linfenliang

     * @date 2012-7-23

     * @version V1.0.0

     * void

     */

    public static void httpServerShutDown(){

        HTTPSERVER_BOOTSTRAP.releaseExternalResources();

        LOGGER.info("HTTP服务已关闭");

    }


 

2.HTTP管道工厂的设置

其关键代码如下:

 

ChannelPipeline pipeline = Channels.pipeline();

        pipeline.addLast("decoder"new HttpRequestDecoder());

//        pipeline.addLast("aggregator", new HttpChunkAggregator(1048576));//放在decoder以后

        pipeline.addLast("encoder"new HttpResponseEncoder());

//        pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());

        pipeline.addLast("handler"new HTTPServerHandler());    

//        设置好aggregatorchunkedWriter,可上传、下载附件,调用如下

//        Channel ch = ...;

//         ch.write(new ChunkedFile(new File("video.mkv"));

//

        //以下是HTTPS的设置

        SSLEngine engine = HttpSslContextFactory.getServerContext().createSSLEngine();

        engine.setUseClientMode(false);    //非客户端模式

        

        pipeline.addLast("ssl"new SslHandler(engine));

        return pipeline;


 

HTTP请求有上传时或者需要给客户端响应文件时,设置aggregatorchunkedWriter,如视频流的下载,音频流的下载等,注意大小不要超出aggregator设置的限制,在ServerBootstrap不要再设置文件大小了。

 

当需要使用安全的连接即HTTPS连接时,需配置SslHandlerChannelPipelineSslHandler无需另外写出,调用NETTY封装的即可,传入的SSLEngine在下面生成

 

package com.lin.socket.ssl;

 

import java.security.KeyStore;

import java.security.Security;

 

import javax.net.ssl.KeyManagerFactory;

import javax.net.ssl.SSLContext;

 

import org.apache.log4j.Logger;

 

 

/****************************************************************************

* com.lin.socket.ssl HttpSslContextFactory.java Created on 2012-9-11

*

* @Author: linfenliang

* @Description:SSL服务器端认证

* @Version: 1.0

***************************************************************************/

public class HttpSslContextFactory {

    private static final Logger LOGGER = Logger.getLogger(HttpSslContextFactory.class);

    private static final String PROTOCOL = "SSLv3";

    /**针对于服务器端配置*/

    private static SSLContext SSLCONTEXT = null;

 

    static {

        // 采用的加密算法

        String algorithm = Security

                .getProperty("ssl.KeyManagerFactory.algorithm");

        if (algorithm == null) {

            algorithm = "SunX509";

        }

 

        SSLContext serverContext = null;

        try {

            //访问Java密钥库,JKSkeytool创建的Java密钥库,保存密钥。

            KeyStore ks = KeyStore.getInstance("JKS");

            ks.load(HttpsKeyStore.getKeyStoreStream(), HttpsKeyStore.getKeyStorePassword());

            

            //创建用于管理JKS密钥库的密钥管理器。

            KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);

            kmf.init(ks, HttpsKeyStore.getCertificatePassword());

 

            //构造SSL环境,指定SSL版本为3.0,也可以使用TLSv1,但是SSLv3更加常用。

            serverContext = SSLContext.getInstance(PROTOCOL);

            

            //初始化SSL环境。第二个参数是告诉JSSE使用的可信任证书的来源,设置为null是从javax.net.ssl.trustStore中获得证书。第三个参数是JSSE生成的随机数,这个参数将影响系统的安全性,设置为null是个好选择,可以保证JSSE的安全性。

            serverContext.init(kmf.getKeyManagers(), null, null);

        } catch (Exception e) {

            LOGGER.error("初始化客户端SSL失败", e);

            throw new Error("Failed to initialize the server SSLContext", e);

        }

 

        SSLCONTEXT = serverContext;

    }

 

    /**

     * 获取SSLContext实例

     *

     * @author linfenliang

     * @date 2012-9-11

     * @version V1.0.0

     * @return

     * SSLContext

     */

    public static SSLContext getServerContext() {

        

        return SSLCONTEXT ;

    }

 

}


以上类即为配置的生产SSLContext的工厂,我们已经将我们生成的安全密钥以流的方式加载进KeyStore中了,密码是以CharArray的方式加载的,获取密钥和密码的方法在类HttpsKeyStore中,其主要功能方法如下:

 

/**

     * 读取密钥

     *

     * @author linfenliang

     * @date 2012-9-11

     * @version V1.0.0

     * @return InputStream

     */

    public static InputStream getKeyStoreStream() {

        InputStream inStream = null;

        try {

            inStream = new FileInputStream(Constants.KEYSTORE_PATH);

        } catch (FileNotFoundException e) {

            LOGGER.error("读取密钥文件失败", e);

        }

        return inStream;

    }

 

    /**

     * 获取安全证书密码 (用于创建KeyManagerFactory)

     *

     * @author linfenliang

     * @date 2012-9-11

     * @version V1.0.0

     * @return char[]

     */

    public static char[] getCertificatePassword() {

        return Constants.CERTIFICATEPASSWORD.toCharArray();

    }

 

    /**

     * 获取密钥密码(证书别名密码) (用于创建KeyStore)

     *

     * @author linfenliang

     * @date 2012-9-11

     * @version V1.0.0

     * @return char[]

     */

    public static char[] getKeyStorePassword() {

        return Constants.KEYSTOREPASSWORD.toCharArray();

    }


 

3.消息的处理

 

对于HttpServerHander(自定义类)中的MessageReceived方法,消息处理关键代码如下:

 

       

 // 判断消息类型,解析 消息

        Object msg = e.getMessage();

        HttpRequest request = (HttpRequest) e.getMessage();

        

//        //数据提交方式验证

//        if (request.getMethod() != HttpMethod.GET) {

// sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);

// return;

// }

 

        String uri = request.getUri();

        uri = URLDecoder.decode(uri, Constants.UTF8ENCODER);

ChannelBuffer buffer=new DynamicChannelBuffer(1024);

HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

                log.info(msg);

                buffer.writeBytes(("测试SSL").getBytes());

                response.setContent(buffer);

                response.setHeader("Content-Type""text/html; charset=UTF-8");

response.setHeader("Content-Length", response.getContent().writerIndex());

                Channel ch = ctx.getChannel();

                ch.write(response);

                ch.close().awaitUninterruptibly();


  

注意:(1)对于获取到的Uriget方式提交的数据),其编码方式为URL编码,需要对其进行解码,解码方式URLDecoder.decode(uri, Constants.UTF8ENCODER);

(2)将设置好的response写入到Channel中即可在浏览器中返回

4一些异常的处理

 

对于异常的处理:当出现异常时,可参考调用如下方法:

/**

     * 异常后返回给客户端的信息

     *

     * @author linfenliang

     * @date 2012-9-11

     * @version V1.0.0

     * @param ctx

     * @param status 返回的页面状态

     * void

     */

private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {

HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);

response.setHeader("Content-Type""text/plain; charset=UTF-8");

response.setHeader("Content-Length", response.getContent().writerIndex());

 

ChannelBuffer buffer=new DynamicChannelBuffer(1024);

buffer.writeBytes(("请求失败: " + status.toString() + "\r\n").getBytes());

response.setContent(buffer);

ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);

}


调用此方法的时机一般为在exceptionCaught方法中,通过判断异常的类型,如

if (e.getCause() instanceof TooLongFrameException) {

//调用。。。

}


 

5.未解决的问题

 

1.当有多个HTTP请求时,都会提交到同一个地址,且由一个方法接收,这会对服务器造成较大压力,且可能会产生并发问题

 

(1)对于不同请求区分的问题的一种解决方式:

可通过在端口后面加参数的方式配置

如:

http://127.0.0.1:8888/REGISTER

 

http://127.0.0.1:8888/LOGIN

 

如果后面跟的是汉字,注意采用URLDecoder解码

 

2.关于客户端

 

本文档中的请求方式为客户端浏览器直接访问,对于基于代码的客户端尚未设置

 

3.关于数据在传输过程中的压缩

 

可在管道工厂中添加如下代码

 

//数据压缩

        pipeline.addLast("deflater"new HttpContentCompressor());


注意此代码需放到handler之前

 

对于浏览器无需配置,但对于代码写的客户端,解压缩是否也无需配置尚未可知

0 0
原创粉丝点击