Netty的restful API 簡單實現和部署
1 BEGIN
Netty 是一個基於NIO的客戶,伺服器端程式設計框架,使用Netty 可以確保你快速和簡單的開發出一個網路應用,例如實現了某種協議的客戶,服務端應用。Netty相當簡化和流線化了網路應用的程式設計開發過程,例如,TCP和UDP的socket服務開發。
Netty是一個非同步服務端網路程式設計框架,使用netty可以快速開發出需要的服務。目前有不少公司使用netty開發遊戲伺服器。Netty的高效性吸引了不少開發者的注意。
由於Netty不是一個專門的web/Restful伺服器框架,所有使用Netty開發Restful服務需要自己新增一些額外的模組。這裡簡單實現一份restful 服務,用於計算身體質量指數和基礎代謝率(Basal Metabolic Rate,簡稱BMR),並部署到VPS上。
BMI指數(身體質量指數,簡稱體質指數又稱體重指數,英文為Body Mass Index,簡稱BMI。
基礎代謝率(Basal Metabolic Rate,簡稱BMR)是指:我們在安靜狀態下(通常為靜臥狀態)消耗的最低熱量,人的其他活動都建立在這個基礎上。
2 啟動 ServerBootstrap(建立連線)
到netty 的官方網站下載netty的jar http://netty.io/,這裡使用的netty版本是4.1.6。下載的壓縮包解壓後,在
/jar/all-in-one
下有nettyall-4.1.6.Final.jar
檔案,這個就是netty編譯出來的jar。使用Eclipse新建一個工程,在工程中新建一個
lib
的資料夾,把上面的jar放入資料夾中,並通過Build Path
把jar 加入到工程中。這個專案使用了orgJson作為json的解析庫,同樣的把org-json-20160810.jar
加入工程中(org-json-Jar下載地址)。
新建一個Java類MainServer,加入 ServerBootstrap的啟動程式碼。這部分程式碼源自Netty 的Http Example,所有的Netty 服務啟動程式碼和這類似。
package com.health;
import io.netty.bootstrap.ServerBootstrap ;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* 服務的主入口
* @author superzhan
*
*/
public final class MainServer {
/*是否使用https協議*/
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "6789"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
} else {
sslCtx = null;
}
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ServerInitializer(sslCtx));
Channel ch = b.bind(PORT).sync().channel();
System.err.println("Open your web browser and navigate to " +
(SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
3 ChannelInitializer(初始化連線)
在工程中新建一個class ServerInitializer,用於連線的初始化。
package com.health;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslContext;
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public ServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new HttpServerCodec());/*HTTP 服務的解碼器*/
p.addLast(new HttpObjectAggregator(2048));/*HTTP 訊息的合併處理*/
p.addLast(new HealthServerHandler()); /*自己寫的伺服器邏輯處理*/
}
}
4 ChannelHandler(業務控制器)
以上兩份程式碼是固定功能的框架程式碼,業務控制器Handler才是自有發揮的部分。
- 需要獲取客戶端的請求uri做路由分發,不同的請求做不同的響應。
- 把客戶端的請求資料解析成Json物件,方便做運算。
- 把計算好的結果生成一個Json 資料發回客戶端。
package com.health;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
import org.json.JSONObject;
public class HealthServerHandler extends ChannelInboundHandlerAdapter {
private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type");
private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length");
private static final AsciiString CONNECTION = new AsciiString("Connection");
private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive");
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
FullHttpRequest req = (FullHttpRequest) msg;//客戶端的請求物件
JSONObject responseJson = new JSONObject();//新建一個返回訊息的Json物件
//把客戶端的請求資料格式化為Json物件
JSONObject requestJson = null;
try{
requestJson = new JSONObject(parseJosnRequest(req));
}catch(Exception e)
{
ResponseJson(ctx,req,new String("error json"));
return;
}
String uri = req.uri();//獲取客戶端的URL
//根據不同的請求API做不同的處理(路由分發),只處理POST方法
if (req.method() == HttpMethod.POST) {
if(req.uri().equals("/bmi"))
{
//計算體重質量指數
double height =0.01* requestJson.getDouble("height");
double weight =requestJson.getDouble("weight");
double bmi =weight/(height*height);
bmi =((int)(bmi*100))/100.0;
responseJson.put("bmi", bmi +"");
}else if(req.uri().equals("/bmr"))
{
//計算基礎代謝率
boolean isBoy = requestJson.getBoolean("isBoy");
double height = requestJson.getDouble("height");
double weight = requestJson.getDouble("weight");
int age = requestJson.getInt("age");
double bmr=0;
if(isBoy)
{
//66 + ( 13.7 x 體重kg ) + ( 5 x 身高cm ) - ( 6.8 x 年齡years )
bmr = 66+(13.7*weight) +(5*height) -(6.8*age);
}else
{
//655 + ( 9.6 x 體重kg ) + ( 1.8 x 身高cm ) - ( 4.7 x 年齡years )
bmr =655 +(9.6*weight) +1.8*height -4.7*age;
}
bmr =((int)(bmr*100))/100.0;
responseJson.put("bmr", bmr+"");
}else {
//錯誤處理
responseJson.put("error", "404 Not Find");
}
} else {
//錯誤處理
responseJson.put("error", "404 Not Find");
}
//向客戶端傳送結果
ResponseJson(ctx,req,responseJson.toString());
}
}
/**
* 響應HTTP的請求
* @param ctx
* @param req
* @param jsonStr
*/
private void ResponseJson(ChannelHandlerContext ctx, FullHttpRequest req ,String jsonStr)
{
boolean keepAlive = HttpUtil.isKeepAlive(req);
byte[] jsonByteByte = jsonStr.getBytes();
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(jsonByteByte));
response.headers().set(CONTENT_TYPE, "text/json");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, KEEP_ALIVE);
ctx.write(response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
/**
* 獲取請求的內容
* @param request
* @return
*/
private String parseJosnRequest(FullHttpRequest request) {
ByteBuf jsonBuf = request.content();
String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8);
return jsonStr;
}
}
5 執行測試
再Eclipse 中直接Run as Java Application,就可以開啟Netty的服務。Netty 的服務不需要放到任何容器中,可以單獨執行。
這裡使用google 的PostMan測試。
6 匯出jar
netty服務向外釋出的時候不能只在Eclipse 中Run as Application。 所以釋出服務的時候需要匯出可執行的jar,然後執行這個jar檔案。
在Eclipse中右擊當前工程->Export->在Export視窗中選擇Java選項下的Runnable Jar File ->Next -> 在視窗中選擇匯出的檔案路徑和啟動的入口類 -> Finish。
開啟命令列終端,切換到當前目錄,執行 java -jar Server.jar
,便可以啟動服務了(Server.jar是匯出來的jar)。可以通過PostMan來測試。
7 把程式碼部署在VPS上
實際使用的Restful服務需要部署在某臺伺服器上。在這裡我把Server.jar 部署在搬瓦工VPS上。VPS安裝的系統是Centos 6 x86 minimal。系統本身沒有安裝JDK,需用通過yum 命令安裝openjdk。
yum list java*
列出所有的openjdk版本。這裡通過yum 安裝jdk1.8yum install java-1.8.0-openjdk.i686
, 通過java -version
命令檢視openjdk版本。Server.jar 可以通過sftp上傳的vps伺服器上,這裡使用Cyberduck這款mac系統下的軟體。或者可以通過Linux 的SCP 命令把Jar上傳到vps 伺服器上,具體操作可以參照http://blog.csdn.net/marujunyy/article/details/8809481
Linux 的命令列視窗實際上的單任務模式的,如果直接執行jar,關掉視窗之後,jar也會自動關掉,不會長期駐留伺服器上。 這時候需要用到 Screen 這個多視窗管理軟體(可通過yum安裝)。
screen -S HealthServer
新開一個視窗,執行java -jar Server.jar
執行服務,這樣服務就可以作為一個獨立的任務執行單獨執行。可以通過快捷鍵ctl+a+d切換回主視窗。screen -ls
命令可以列出當前所有的視窗任務。screen -r HealthServer
命令切換到服務執行的視窗。
8 The End
最終的介面可以通過PostMan來做測試。