背景:java 微服务包括https访问和websocket访问,当https接口访问ws请求时报错,因为https能访问wss。
申请阿里云免费证书后,搜索各种教程比如nginx配置方式、netty访问证书等。走了不少弯路,终于走通一种。
关键点:1、因为使用了netty,nginx配置wss的方式没有走通。需要将证书放到netty启动的方式启动才可以。
2、网上教程大多数是pkcs12的证书生成方式。但是netty仅仅支持pkcs8 的版本,所以需要生成pkcs12之后再转pkcs8.
一、阿里云申请免费证书3、因为netty仅仅支持pkcs8,所以将server.key 通过OpenSSl 生成pkcs8的版本
安装openssl
windows电脑下载已经编译好的openssl
OpenSSL for Windows
或者
OpenSSL官方下载 - 码客
然后将安装路径加到环境变量中
cmd 命令生成server8.key:
openssl pkcs8 -topk8 -inform PEM -in server.key -outform pem -nocrypt -out server8.key三、编写代码
仅仅需要增加两个地方:
1、启动的start方法,增加server8.key和pem文件的读取全局变量
private SslContext sslContext;//netty 配置ssl证书、WSS地址访问。备注:netty 仅仅使用pkcs8的证书,需要使用openssl转将证书放到main/resources下面,则用ClassPathResource读取
//ssl证书配置--放到项目中 ClassPathResource pem = new ClassPathResource("service.sv3d.cn.pem"); ClassPathResource key = new ClassPathResource("server8.key"); this.sslContext = SslContextBuilder.forServer(pem.getInputStream(),key.getInputStream()).build();将证书放到外面,方便后期更换,毕竟免费的证书是一年一申请的,用InputStream读取
//ssl证书配置--放到外部 InputStream pemInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_nginx\\9760642_service.sv3d.cn.pem"); /// 证书存放地址 InputStream keyInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_tomcat\\server8.key"); /// 证书存放地址 this.sslContext = SslContextBuilder.forServer(pemInputStream,keyInputStream).build(); 2、initChannel 方法添加ssl的验证 ch.pipeline().addLast(sslContext.newHandler(ch.alloc())); 3、全部代码,仅看修改部分就行,大同小异。 /** * 启动 * @throws InterruptedException */ private void start() throws InterruptedException { try { port = Integer.parseInt(optionService.getByKey(OptionKey.SocketPort.getKey()).getValue()); String wsFlag = nacosConfigProp.getWsFlag(); //ssl证书配置--放到外部 //InputStream pemInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_nginx\\9760642_service.sv3d.cn.pem"); /// 证书存放地址 //InputStream keyInputStream = new FileInputStream("E:\\zhengshu\\9760642_service.sv3d.cn_tomcat\\server8.key"); /// 证书存放地址 //this.sslContext = SslContextBuilder.forServer(pemInputStream,keyInputStream).build(); //ssl证书配置--放到项目中 ClassPathResource pem = new ClassPathResource("service.sv3d.cn.pem"); ClassPathResource key = new ClassPathResource("server8.key"); this.sslContext = SslContextBuilder.forServer(pem.getInputStream(),key.getInputStream()).build(); } catch (Exception e) { e.printStackTrace(); } //数据量上来时设置线程池 /*bossGroup = new NioEventLoopGroup(2,new DefaultThreadFactory("server1",true)); workGroup = new NioEventLoopGroup(4,new DefaultThreadFactory("server2",true));*/ // 获取Reactor线程池 bossGroup = new NioEventLoopGroup(); workGroup = new NioEventLoopGroup(); // 服务端启动辅助类,用于设置TCP相关参数 ServerBootstrap bootstrap = new ServerBootstrap(); // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作,设置为主从线程模型 bootstrap.group(bossGroup,workGroup); //禁用nagle算法 bootstrap.childOption(ChannelOption.TCP_NODELAY, true); //bootstrap.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024*1024, 40*1024*1024)); //当设置为true的时候,TCP会实现监控连接是否有效,当连接处于空闲状态的时候,超过了2个小时, //本地的TCP实现会发送一个数据包给远程的 socket,如果远程没有发回响应,TCP会持续尝试11分钟, //知道响应为止,如果在12分钟的时候还没响应,TCP尝试关闭socket连接。 bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // 设置NIO类型的channel,设置服务端NIO通信类型 bootstrap.channel(NioServerSocketChannel.class); // 设置监听端口 bootstrap.localAddress(new InetSocketAddress(port)); // 连接到达时会创建一个通道,设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理 bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { // 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等 @Override protected void initChannel(SocketChannel ch) throws Exception { // 流水线管理通道中的处理程序(Handler),用来处理业务 //=============================增加心跳支持============================ //添加ssl 访问 ch.pipeline().addLast(sslContext.newHandler(ch.alloc())); // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器 ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new ObjectEncoder()); //ch.pipeline().addLast(myMessageDncoder); //ch.pipeline().addLast(myMessageEncoder); // 以块的方式来写的处理器,分块向客户端写数据,防止发送大文件时导致内存溢出,channel.write(new ChunkedFile(new File("..."))) ch.pipeline().addLast(new ChunkedWriteHandler()); //ch.pipeline().addLast(new MyServerChunkHandler()); /* 说明: 1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合 2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求 */ //需要放到HttpServerCodec后面 ch.pipeline().addLast(new HttpObjectAggregator(65535));//10kb? //websocket数据压缩扩展,当添加这个的时候,WebSocketServerProtocolHandler第三个参数需要设置成true ch.pipeline().addLast(new WebSocketServerCompressionHandler()); /* 说明: 1、对应webSocket,它的数据是以帧(frame)的形式传递 2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri 3、核心功能是将http协议升级为ws协议,保持长连接 */ //对客户端,如果在60秒内没有向服务端发送心跳,就主动断开 //三个参数分别为读/写/读写的空闲,我们只针对读写空闲检测 //ch.pipeline().addLast(new IdleStateHandler(10,11,12,TimeUnit.SECONDS)); //ch.pipeline().addLast(heartBeatHandler); // 自定义的handler,处理业务逻辑 //ch.pipeline().addLast(webSocketHandler); //ch.pipeline().addLast(userLoginRespHandler); ch.pipeline().addLast(textWebSocketHandler); ch.pipeline().addLast(binaryWebSocketFrameHandler); // 服务器端向外暴露的 web socket 端点,当客户端传递比较大的对象时,maxFrameSize参数的值需要调大 ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, webSocketProtocol, true, 10485760));//10mb? } }); // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功 ChannelFuture channelFuture = bootstrap.bind().sync(); log.info("Server started and listen on:{}",channelFuture.channel().localAddress()); // 对关闭通道进行监听 channelFuture.channel().closeFuture().sync(); } 四、验证是否通,采用Postman或者自己写界面都行。配置完了,发现ws不能使用了,还在研究,各位大神有办法,可以留言,互相学习。下面是下载的别人的案例,然后改了下我的案例,所以里面有两个server,可以做下参考。
java访问案例
另一种方式是WebsocketServer生成key然后配置密码的方式,我尝试过,生成的密码每次都是不匹配,所以放弃了。