为什么会想通过Netty构建一个HTTP代理服务器?这也是笔者发表这篇文章的目的所在。

  其主要还是源于解决在日常开发测试过程中,一直困扰测试同学很久的一个问题,现在我就来具体阐述一下这个问题。

  在日常开发测试过程中,为了确保上线项目的万无一失,集成测试通常有部署,测试环境和回归环境这两套环境。开发人员根据需求编写代码模块,自测通过之后,由测试的同学更新到测试环境,进行测试。如果测试通过,确定项目具备上线条件,后续会在回归环境,进行回归测试。回归验证通过的项目,才具备上线条件。

  由于模块的复杂性和多样性,我们系统要跟外系统进行一些数据的交互,这通常是通过HTTP协议方式完成的。现在由于某些条件的限制,通常只有测试环境的网络和端口是和外系统是打通的,回归环境的这块网络链路是关闭的。这样就产生了一个很尴尬的问题:如果一个模块有跟外系统进行交互,回归环境是不具备回归条件的,这样就要测试的同学,额外把模块更新到测试环境来验证,这样不仅耗时耗力。并且由于测试环境和回归环境系统数据的差异,往往可能导致项目的潜在风险没有被及时地发现。

  现在迫切希望有一个HTTP代理服务器,能够路由回归环境的请求到测试环境。更进一步地,如果能根据请求报文的某些关键字来过滤,决定最终路由的地址,这个当然是最好了。

  基于这些因素,考虑到HTTP代理服务器的主要用途是转发URL请求,可选的方案有很多种。比如Apache、Nginx等等。但是最终都没有被采用,主要基于以下几点考虑:

  1. Apache服务器不能根据某些指定的关键字过滤转发URL请求,只能做简单的代理转发。
  2. Nginx相比Aapche服务器单纯进行请求转发而言,通过OpenResty(http://openresty.org/cn/),可以把lua解析器内嵌到Nginx,这样可以编写lua脚本的关键字过滤规则,但是要测试同学短时间内学会配置不太现实。

  有没有通过简单的几个配置,就可以达到目的的可行方案呢?

  我首先想到了使用Netty这个NIO框架,来实现一个轻量级的HTTP代理转发服务器,同时只要简单地配置过滤规则,就可以实现请求的规则路由。

  本文要求你熟悉Netty网络框架的工作流程,基本原理。有兴趣的朋友,可以认真研读一下《Netty in Action》这本书,对提高Netty的功力有很大帮助。

  言归正传,下面是这个HTTP代理转发服务器的工作流程图:

  这里我简单描述一下:

  • 首先是Netty的服务端连接器(Acceptor)线程接收到HTTP请求,然后会把这个请求放入后端Netty专门负责处理I/O操作的线程池中。这个也是Netty经典的主从Reactor多线程模型的应用。
  • I/O处理线程先对HTTP请求,调用HttpRequestDecoder解码器进行解码。
  • HttpRequestDecoder把解码的结果,通知给路由规则计算的核心模块(GatewayServerHandler),核心模块根据配置加上请求报文中的关键字,计算出要转发的URL地址。
  • 通过HTTP POST方式把请求,转发给计算出来的URL地址。
  • 获取HTTP POST的获得到的应答结果。
  • 然后通过HttpResponseEncoder编码器,把应答结果进行HTTP编码,最后透传给调用方。

  流程描述很简单,现在关键是,如何设计关键字路由规则配置模块。

  我是通过属性配置文件(.properties)方式来实现的,主要有两个配置文件。

  • netty-gateway.properties配置文件,主要是用来描述URL中的路径、以及其没有和请求URL路径匹配成功时,默认转发的URL地址。

  配置文件的配置参考说明:

#配置说明参考:
#netty-gateway.config1.serverPath ==> URL路径关键字。
#netty-gateway.config1.defaultAddr ==> 请求报文中的关键字没有匹配成功时,默认转发的URL地址。
#config的数字后缀顺序递增即可。 netty-gateway.config1.serverPath=fcgi-bin/UIG_SFC_186
netty-gateway.config1.defaultAddr=http://10.46.158.10:8088/fcgi-bin/UIG_SFC_186 netty-gateway.config2.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config2.defaultAddr=http://10.46.158.10:8089/fcgi-bin/BSSP_SFC
  • netty-route.properties配置文件,则是主要配置URL中的路径、请求报文关键字集合、以及请求的URL路径、请求报文关键字和配置的匹配成功时,转发的URL地址。

  配置文件的配置参考说明:

#配置说明参考:
#netty-gateway.config1.serverPath ==> URL路径关键字。
#netty-gateway.config1.keyWord ==> 请求报文匹配关键字。支持1~N个关键字,多个关键字用逗号分割,关键字之间是逻辑与的关系。
#netty-gateway.config1.matchAddr ==> 请求报文匹配关键字匹配成功时,转发的ULR地址。
#config的数字后缀顺序递增即可。 netty-gateway.config1.serverPath=fcgi-bin/UIG_SFC_186
netty-gateway.config1.keyWord=1,2,3
netty-gateway.config1.matchAddr=http://10.46.158.20:8088/fcgi-bin/UIG_SFC_186 netty-gateway.config2.serverPath=fcgi-bin/UIG_SFC_186
netty-gateway.config2.keyWord=1,2,3,4
netty-gateway.config2.matchAddr=http://10.46.158.20:8088/fcgi-bin/UIG_SFC_186 netty-gateway.config3.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config3.keyWord=HelloWorldNettyGateway
netty-gateway.config3.matchAddr=http://10.46.158.20:8089/fcgi-bin/BSSP_SFC

  有了上述两个基础的配置信息之后,就可以实现基于Netty的关键字HTTP路由转发服务器了。

  这里主要说明关键代码模块的设计思路:

  首先是GatewayAttribute类,它主要对应netty-gateway.properties配置文件的数据结构。

package com.newlandframework.gateway.commons;

/**
* @author tangjie<https://github.com/tang-jie>
* @filename:GatewayAttribute.java
* @description:GatewayAttribute功能模块
* @blogs http://www.cnblogs.com/jietang/
* @since 2018/4/18
*/
public class GatewayAttribute {
private String serverPath;
private String defaultAddr; public String getDefaultAddr() {
return defaultAddr;
} public void setDefaultAddr(String defaultAddr) {
this.defaultAddr = defaultAddr;
} public String getServerPath() {
return serverPath;
} public void setServerPath(String serverPath) {
this.serverPath = serverPath;
}
}

  其次是RouteAttribute类,它主要对应netty-route.properties配置文件的数据结构。

package com.newlandframework.gateway.commons;

/**
* @author tangjie<https://github.com/tang-jie>
* @filename:RouteAttribute.java
* @description:RouteAttribute功能模块
* @blogs http://www.cnblogs.com/jietang/
* @since 2018/4/18
*/
public class RouteAttribute {
private String serverPath;
private String keyWord;
private String matchAddr; public String getMatchAddr() {
return matchAddr;
} public void setMatchAddr(String matchAddr) {
this.matchAddr = matchAddr;
} public String getServerPath() {
return serverPath;
} public void setServerPath(String serverPath) {
this.serverPath = serverPath;
} public String getKeyWord() {
return keyWord;
} public void setKeyWord(String keyWord) {
this.keyWord = keyWord;
}
}

  然后通过实现spring框架的BeanDefinitionRegistryPostProcessor接口,来实现配置文件的自动加载注入。对应代码如下:

package com.newlandframework.gateway.commons;

import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import java.io.IOException;
import java.util.*; import static com.newlandframework.gateway.commons.GatewayOptions.*; /**
* @author tangjie<https://github.com/tang-jie>
* @filename:RoutingLoader.java
* @description:RoutingLoader功能模块
* @blogs http://www.cnblogs.com/jietang/
* @since 2018/4/18
*/
public class RoutingLoader implements BeanDefinitionRegistryPostProcessor {
public static final List<RouteAttribute> ROUTERS = new ArrayList<RouteAttribute>();
public static final List<GatewayAttribute> GATEWAYS = new ArrayList<GatewayAttribute>(); private static final List<String> KEY_ROUTERS = new ArrayList<String>();
private static final List<String> KEY_GATEWAYS = new ArrayList<String>(); @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
initGatewayRule(registry);
initRouteRule(registry);
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
GATEWAYS.clear();
ROUTERS.clear(); for (String beanName : KEY_GATEWAYS) {
GATEWAYS.add(beanFactory.getBean(beanName, GatewayAttribute.class));
} for (String beanName : KEY_ROUTERS) {
ROUTERS.add(beanFactory.getBean(beanName, RouteAttribute.class));
}
} //加载netty-gateway.properties配置文件
private void initGatewayRule(BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
Resource resource = new ClassPathResource(GATEWAY_OPTION_GATEWAY_CONFIG_FILE);
Properties p = new Properties();
try {
p.load(resource.getInputStream()); String key = null;
String keyPrefix = null;
String defaultAddr = null;
String serverPath = null; Map<String, String> valuesMap = null;
MutablePropertyValues mpv = null; for (Object obj : p.keySet()) {
key = obj.toString();
if (key.endsWith(GATEWAY_PROPERTIES_PREFIX_SERVER_PATH)) {
keyPrefix = key.substring(0, key.indexOf(GATEWAY_PROPERTIES_PREFIX_SERVER_PATH));
serverPath = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_SERVER_PATH).trim();
defaultAddr = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_DEFAULT_ADDR).trim(); valuesMap = new LinkedHashMap<String, String>();
valuesMap.put(GATEWAY_PROPERTIES_DEFAULT_ADDR, defaultAddr);
valuesMap.put(GATEWAY_PROPERTIES_SERVER_PATH, serverPath); mpv = new MutablePropertyValues(valuesMap);
beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(GatewayAttribute.class);
beanDefinition.setPropertyValues(mpv);
registry.registerBeanDefinition(serverPath, beanDefinition); KEY_GATEWAYS.add(serverPath);
}
}
} catch (IOException e) {
e.printStackTrace();
}
} //加载netty-route.properties配置文件
private void initRouteRule(BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
Resource resource = new ClassPathResource(GATEWAY_OPTION_ROUTE_CONFIG_FILE);
Properties p = new Properties(); try {
p.load(resource.getInputStream()); String key = null;
String keyPrefix = null;
String keyWord = null;
String matchAddr = null;
String serverPath = null; Map<String, String> valuesMap = null;
MutablePropertyValues mpv = null; for (Object obj : p.keySet()) {
key = obj.toString();
if (key.endsWith(GATEWAY_PROPERTIES_PREFIX_KEY_WORD)) {
keyPrefix = key.substring(0, key.indexOf(GATEWAY_PROPERTIES_PREFIX_KEY_WORD));
keyWord = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_KEY_WORD).trim();
if (keyWord.isEmpty()) continue;
matchAddr = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_MATCH_ADDR).trim();
serverPath = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_SERVER_PATH).trim(); valuesMap = new LinkedHashMap<String, String>();
valuesMap.put(GATEWAY_PROPERTIES_KEY_WORD, keyWord);
valuesMap.put(GATEWAY_PROPERTIES_MATCH_ADDR, matchAddr);
valuesMap.put(GATEWAY_PROPERTIES_SERVER_PATH, serverPath); mpv = new MutablePropertyValues(valuesMap);
beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(RouteAttribute.class);
beanDefinition.setPropertyValues(mpv);
String beanName = serverPath + GATEWAY_OPTION_SERVER_SPLIT + keyWord;
registry.registerBeanDefinition(beanName, beanDefinition); KEY_ROUTERS.add(beanName);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

  最后是重点的关键字过滤转发代码模块,主要完成路由转发地址的匹配计算、路由转发、以及应答转发结果给请求客户端的工作。

import com.newlandframework.gateway.commons.GatewayAttribute;
import com.newlandframework.gateway.commons.HttpClientUtils;
import com.newlandframework.gateway.commons.RouteAttribute;
import com.newlandframework.gateway.commons.RoutingLoader;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.Signal;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.util.StringUtils; import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import static com.newlandframework.gateway.commons.GatewayOptions.*;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /**
* @author tangjie<https://github.com/tang-jie>
* @filename:GatewayServerHandler.java
* @description:GatewayServerHandler功能模块
* @blogs http://www.cnblogs.com/jietang/
* @since 2018/4/18
*/
public class GatewayServerHandler extends SimpleChannelInboundHandler<Object> {
private HttpRequest request;
private StringBuilder buffer = new StringBuilder();
private String url = "";
private String uri = "";
private StringBuilder respone;
private GlobalEventExecutor executor = GlobalEventExecutor.INSTANCE;
private CountDownLatch latch = new CountDownLatch(1); @Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
} @Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
HttpRequest request = this.request = (HttpRequest) msg; //收到客户端的100-Continue协议请求,说明客户端要post数据给服务器
if (HttpUtil.is100ContinueExpected(request)) {
notify100Continue(ctx);
} buffer.setLength(0);
uri = request.uri().substring(1);
} if (msg instanceof HttpContent) {
HttpContent httpContent = (HttpContent) msg;
ByteBuf content = httpContent.content();
if (content.isReadable()) {
buffer.append(content.toString(GATEWAY_OPTION_CHARSET));
} //获取post数据完毕
if (msg instanceof LastHttpContent) {
LastHttpContent trace = (LastHttpContent) msg; System.out.println("[NETTY-GATEWAY] REQUEST : " + buffer.toString()); //根据netty-gateway.properties、netty-route.properties匹配出最终转发的URL地址
url = matchUrl();
System.out.println("[NETTY-GATEWAY] URL : " + url); //http请求异步转发处理,不要阻塞当前的Netty Handler的I/O线程,提高服务器的吞吐量。
Future<StringBuilder> future = executor.submit(new Callable<StringBuilder>() {
@Override
public StringBuilder call() {
return HttpClientUtils.post(url, buffer.toString(), GATEWAY_OPTION_HTTP_POST);
}
}); future.addListener(new FutureListener<StringBuilder>() {
@Override
public void operationComplete(Future<StringBuilder> future) throws Exception {
if (future.isSuccess()) {
respone = ((StringBuilder) future.get(GATEWAY_OPTION_HTTP_POST, TimeUnit.MILLISECONDS));
} else {
respone = new StringBuilder(((Signal) future.cause()).name());
}
latch.countDown();
}
}); try {
latch.await();
writeResponse(respone, future.isSuccess() ? trace : null, ctx);
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
} //根据netty-gateway.properties、netty-route.properties匹配出最终转发的URL地址
private String matchUrl() {
for (GatewayAttribute gateway : RoutingLoader.GATEWAYS) {
if (gateway.getServerPath().equals(uri)) {
for (RouteAttribute route : RoutingLoader.ROUTERS) {
if (route.getServerPath().equals(uri)) {
String[] keys = StringUtils.delimitedListToStringArray(route.getKeyWord(), GATEWAY_OPTION_KEY_WORD_SPLIT);
boolean match = true;
for (String key : keys) {
if (key.isEmpty()) continue;
if (buffer.toString().indexOf(key.trim()) == -1) {
match = false;
break;
}
}
if (match) {
return route.getMatchAddr();
}
}
} return gateway.getDefaultAddr();
}
}
return GATEWAY_OPTION_LOCALHOST;
} //把路由转发的结果应答给http客户端
private void writeResponse(StringBuilder respone, HttpObject current, ChannelHandlerContext ctx) {
if (respone != null) {
boolean keepAlive = HttpUtil.isKeepAlive(request); FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, current == null ? OK : current.decoderResult().isSuccess() ? OK : BAD_REQUEST,
Unpooled.copiedBuffer(respone.toString(), GATEWAY_OPTION_CHARSET)); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=GBK"); if (keepAlive) {
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} ctx.write(response);
}
} private static void notify100Continue(ChannelHandlerContext ctx) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE);
ctx.write(response);
}
}

  这样把整个工程maven打包部署运行,服务器默认启动端口8999,你可以通过netty-gateway.xml的gatewayPort属性进行配置调整。

  控制台打印出如下的信息,则说明服务器启动成功。

  下面继续以一个实际的案例来说明一下,如何配置使用这个HTTP服务器。

  NettyGateway代理转发场景描述

  • NettyGateway部署在10.1.1.76主机,URL中的路径为:fcgi-bin/BSSP_SFC
  • 如果请求报文中出现HelloWorldNettyGateway关键字的时候,转发到http://10.46.158.20:8089/fcgi-bin/BSSP_SFC
  • 否则转发到http://10.46.158.10:8089/fcgi-bin/BSSP_SFC

  NettyGateway代理转发场景配置说明

  • 配置文件netty-gateway.properties新增如下属性:
netty-gateway.config2.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config2.defaultAddr=http://10.46.158.10:8089/fcgi-bin/BSSP_SFC
  • 配置文件netty-route.properties新增如下属性:
netty-gateway.config3.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config3.keyWord=HelloWorldNettyGateway
netty-gateway.config3.matchAddr=http://10.46.158.20:8089/fcgi-bin/BSSP_SFC

  NettyGateway代理转发测试

  • 发送HelloWorldNettyGateway到NettyGateway,关键字匹配成功,路由到http://10.46.158.20:8089/fcgi-bin/BSSP_SFC

  • 发送Tangjie到NettyGateway,关键字匹配不成功,路由到默认的http://10.46.158.10:8089/fcgi-bin/BSSP_SFC

  到此,整个基于Netty实现的,一个轻量级HTTP代理服务器的主要设计思路已经介绍完了。整个服务器实现代码非常的少,而且通过简单地配置,就能很好的满足实际要求。相比通过“重量级”的服务器:Apache、Nginx,进行HTTP代理转发而言,提供了另外一种解决问题的思路。在部门的实际部署运行中,这个Netty写的小而精的服务器,运行良好,很好地帮助测试部门的同学,解决了一个困扰他们很久的问题。

  俗话说得好,黑猫、白猫,抓到老鼠就是好猫。我把这个基于Netty的HTTP代理服务器取名“NettyGateway”,目前把代码托管在github上面:https://github.com/tang-jie/NettyGateway

  有兴趣的朋友,可以关注一下。由于技术能力所限,文中难免有纰漏和不足,大家如果有疑问,欢迎在下方的博客园评论区留言,或者通过github提issue给我。

  最后,感谢您耐心阅读。如果喜欢本文的话,可以点击推荐,算是给我一个小小的鼓励!谢谢大家。

  附上本人曾经在博客园发表的,基于Netty框架实际应用的原创文章,有兴趣的朋友,可以关联阅读。

  基于Netty构建的RPC

  谈谈如何使用Netty开发实现高性能的RPC服务器

  Netty实现高性能RPC服务器优化篇之消息序列化

  基于Netty打造RPC服务器设计经验谈

  基于Netty构建的简易消息队列

  Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇

  Netty构建分布式消息队列实现原理浅析

如何用Netty实现一个轻量级的HTTP代理服务器的更多相关文章

  1. 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇

    基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇 前提 最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换 ...

  2. 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇

    前提 前置文章: Github Page:<基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> Coding Page:<基于Netty和SpringBoot实现 ...

  3. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇

    前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前 ...

  4. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理

    前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> & ...

  5. 一个轻量级分布式RPC框架--NettyRpc

    1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个轻量级的分布式RPC ...

  6. 一个轻量级分布式 RPC 框架 — NettyRpc

    原文出处: 阿凡卢 1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个 ...

  7. 如何在Java生态圈选择一个轻量级的RESTful框架?

    在微服务流行的今天,我们会从纵向和横向分解代码的逻辑,将一些独立的无状态的代码单元实现为微服务,可以将它们发布到一些分布式计算单元或者Docker中,并在性能需要的时候及时地创建更多的服务单元.微服务 ...

  8. [开源] gnet: 一个轻量级且高性能的 Golang 网络库

    Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Ev ...

  9. 手写一个类SpringBoot的HTTP框架:几十行代码基于Netty搭建一个 HTTP Server

    本文已经收录进 : https://github.com/Snailclimb/netty-practical-tutorial (Netty 从入门到实战:手写 HTTP Server+RPC 框架 ...

随机推荐

  1. sql 用临时表时报错 "Chinese_PRC_90_CI_AI" 和 "Chinese_PRC_CI_AS" 之间的排序规则冲突

    在用临时表关联数据库中的表做关联查询时,如果报这种情况的话,就要把临时表和关联的表的排序规则统一掉. LEFT JOIN #tsub ON #tsub.joinjarno collate Chines ...

  2. 分贝块---dBblock

    分贝,用英语来表达的话,是decibel,是量度两个相同单位之数量比例的计量单位,主要用于度量声音强度,常用dB表示. 块,block,在百度百科中,指数据库中的最小存储和处理单位,包含块本身的头信息 ...

  3. spring-oauth-server实践:access_token的有效期分析

    1.access_token有效期检查 用expiration和new Date()比较!!!!!! 分析目标-->expiration什么时候设置,设置规则如何配置!!!!!!! 2.acce ...

  4. 证明二叉查找树所有节点的平均深度为O(logN)

    数据结构与算法分析(c语言描述)第4章 P78 概念一:一棵树所有节点的深度和称为内部路径长 令D(N)为一棵有N节点的树的内部路径长么,即有D(1)=0, 设一棵树的左子树的内部路径长为D(i),则 ...

  5. [洛谷P1197/BZOJ1015][JSOI2008]星球大战Starwar - 并查集,离线,联通块

    Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过 ...

  6. HTML5示例之WebSocket

    Web应用程序通常有一些耗时的操作,但有些操作耗时不是很长,一分钟之内能完成.如果采用后台任务队列去异步处理,这样的用户不能实时看到后台处理的情况.倘若用户触发操作后,Web页面能够实时看到后台处理的 ...

  7. html标记语言 --文本标记

    html标记语言 --文本标记 二.文本标记 1.h1-h6 标题标记,h1最大 2.font 字体设置标记 2.1 size字体大小.<font size="> 取值范围1-7 ...

  8. 关于web XSS注入问题

    对web安全方面的知识非常薄弱,这篇文章把Xss跨站攻击和sql注入的相关知识整理了下,附带公司写的一个filer. 对于防止sql注入发生,我只用过简单拼接字符串的注入及参数化查询,可以说没什么好经 ...

  9. SQL基础----DCL

    在之前的文章已经讲到SQL基础DDL(数据库定义语句 http://www.cnblogs.com/cxq0017/p/6433938.html)和 DML(数据库操作语句 http://www.cn ...

  10. Spting +Spring MVC+spring date jsp +hibernate+jq

    controller 控制页面跳转 处理前台后台数据交互 访问servicedao层:@Repository 数据库访问层 增删改查 jpa规范了hibernate jap仓库 jpa自动解析 方法名 ...