HTTP server so sieťou Netty
1. Prehľad
V tejto príručke sa chystáme implementovať jednoduchý horný server HTTP so sieťou, asynchrónny rámec, ktorý nám dáva flexibilitu pri vývoji sieťových aplikácií v prostredí Java.
2. Zavádzanie servera
Než začneme, mali by sme si byť vedomí základných konceptov Netty, ako sú kanál, obslužná rutina, kódovač a dekodér.
Tu skočíme priamo do bootstrapovania servera, ktorý je väčšinou rovnaký ako server s jednoduchým protokolom:
public class HttpServer {private int port; private static Logger logger = LoggerFactory.getLogger (HttpServer.class); // constructor // main method, same as simple protocol server public void run () throws Exception {... ServerBootstrap b = new ServerBootstrap (); b.group (bossGroup, workerGroup) .channel (NioServerSocketChannel.class) .handler (new LoggingHandler (LogLevel.INFO)) .childHandler (new ChannelInitializer () {@Override protected void initChannel (SocketChannel ch) vyvolá výnimku {ChannelPipeline p = chanel .pipeline (); p.addLast (nový HttpRequestDecoder ()); p.addLast (nový HttpResponseEncoder ()); p.addLast (nový CustomHttpServerHandler ());}}); ...}}
Takže, tu len childHandler sa líši podľa protokolu, ktorý chceme implementovať, čo je pre nás HTTP.
Do potrubia servera pridávame tri obslužné programy:
- Netty HttpResponseEncoder - na serializáciu
- Netty HttpRequestDecoder - na deserializáciu
- Náš vlastný CustomHttpServerHandler - za definovanie správania nášho servera
Pozrime sa ďalej na posledného spracovateľa podrobne.
3. CustomHttpServerHandler
Úlohou nášho spracovateľa je spracovávať prichádzajúce údaje a posielať odpovede.
Poďme si to rozdeliť, aby sme pochopili jeho fungovanie.
3.1. Štruktúra psovoda
CustomHttpServerHandler rozširuje abstrakt Netty SimpleChannelInboundHandler a implementuje svoje metódy životného cyklu: Ako naznačuje názov metódy, channelReadComplete vyprázdni kontext obsluhy po spotrebovaní poslednej správy v kanáli, aby bola k dispozícii pre ďalšiu prichádzajúcu správu. Metóda výnimkaCaught slúži na riešenie prípadných výnimiek. Zatiaľ sme videli iba štandardný kód. Poďme teraz na zaujímavé veci, implementáciu channelRead0. Náš prípad použitia je jednoduchý, server jednoducho transformuje telo žiadosti a parametre dotazu, ak existujú, na veľké písmená. Tu dávame pozor na premietnutie údajov žiadosti do odpovede - robíme to iba na demonštračné účely, aby sme pochopili, ako môžeme pomocou Netty implementovať server HTTP. Tu, správu alebo požiadavku spotrebujeme a nastavíme jej odpoveď podľa odporúčania protokolu (poznač si to RequestUtils je niečo, čo napíšeme za chvíľu): Ako vidíme, keď náš kanál dostane HttpRequest, najskôr skontroluje, či požiadavka očakáva stav 100 Pokračovať. V takom prípade okamžite odpíšeme s prázdnou odpoveďou so stavom ĎALEJ: Potom obslužný program inicializuje reťazec, ktorý sa má odoslať ako odpoveď, a pridá k nemu parametre dotazu žiadosti, ktoré sa majú odoslať späť tak, ako sú. Poďme si teraz definovať metódu formatParams a umiestnite ho do a RequestUtils pomocná trieda: Ďalej po prijatí HttpContent, vezmeme telo žiadosti a prevedieme ju na veľké písmená: Tiež, ak je prijaté HttpContent je a LastHttpContent, pridáme správu na rozlúčku a prípadné koncové hlavičky: Teraz, keď sú naše údaje, ktoré sa majú odoslať, pripravené, môžeme odpoveď napísať do ChannelHandlerContext: V tejto metóde sme vytvorili a FullHttpResponse s verziou HTTP / 1.1 a pridaním údajov, ktoré sme pripravili skôr. Ak má byť žiadosť udržiavaná nažive, alebo inými slovami, ak sa spojenie nemá ukončiť, nastavíme odpoveď spojenie hlavička ako udržať nažive. Inak spojenie uzavrieme. Aby sme otestovali náš server, pošleme niekoľko príkazov cURL a pozrime sa na odpovede. Samozrejme, musíme server spustiť spustením triedy HttpServer pred týmto. Najprv vyvoláme server a poskytneme cookie s požiadavkou: Ako odpoveď dostaneme: Môžeme aj udrieť //127.0.0.1:8080?param1=one z ktoréhokoľvek prehliadača, aby ste videli rovnaký výsledok. Ako náš druhý test pošleme POST s telom ukážkový obsah: Tu je odpoveď: Tentokrát, keďže naša žiadosť obsahovala telo, server ho poslal späť veľkými písmenami. V tomto tutoriáli sme videli, ako implementovať protokol HTTP, najmä server HTTP používajúci Netty. HTTP / 2 v sieti Netty demonštruje implementáciu protokolu HTTP / 2 na serveri. Ako vždy, zdrojový kód je k dispozícii na GitHub.verejná trieda CustomHttpServerHandler rozširuje SimpleChannelInboundHandler {súkromná požiadavka HttpRequest; StringBuilder responseData = nový StringBuilder (); @Override public void channelReadComplete (ChannelHandlerContext ctx) {ctx.flush (); } @Override protected void channelRead0 (ChannelHandlerContext ctx, Object msg) {// implementácia nasledovať} @Override public void exceptionCaught (ChannelHandlerContext ctx, hodná príčina) {cause.printStackTrace (); ctx.close (); }}
3.2. Čítanie kanálu
if (msg instanceof HttpRequest) {HttpRequest request = this.request = (HttpRequest) msg; if (HttpUtil.is100ContinueExpected (požiadavka)) {writeResponse (ctx); } responseData.setLength (0); responseData.append (RequestUtils.formatParams (požiadavka)); } responseData.append (RequestUtils.evaluateDecoderResult (požiadavka)); if (msg instanceof HttpContent) {HttpContent httpContent = (HttpContent) msg; responseData.append (RequestUtils.formatBody (httpContent)); responseData.append (RequestUtils.evaluateDecoderResult (požiadavka)); if (msg instanceof LastHttpContent) {LastHttpContent trailer = (LastHttpContent) msg; responseData.append (RequestUtils.prepareLastResponse (žiadosť, upútavka)); writeResponse (ctx, trailer, responseData); }}
private void writeResponse (ChannelHandlerContext ctx) {FullHttpResponse response = new DefaultFullHttpResponse (HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write (odpoveď); }
StringBuilder formatParams (požiadavka HttpRequest) {StringBuilder responseData = new StringBuilder (); QueryStringDecoder queryStringDecoder = nový QueryStringDecoder (request.uri ()); Mapa
StringBuilder formatBody (HttpContent httpContent) {StringBuilder responseData = nový StringBuilder (); ByteBuf content = httpContent.content (); if (content.isReadable ()) {responseData.append (content.toString (CharsetUtil.UTF_8) .toUpperCase ()) .append ("\ r \ n"); } návrat responseData; }
StringBuilder prepareLastResponse (požiadavka HttpRequest, upútavka LastHttpContent) {StringBuilder responseData = new StringBuilder (); responseData.append ("Dovidenia! \ r \ n"); if (! trailer.trailingHeaders (). isEmpty ()) {responseData.append ("\ r \ n"); for (CharSequence name: trailer.trailingHeaders (). names ()) {for (CharSequence value: trailer.trailingHeaders (). getAll (name)) {responseData.append ("P.S. Trailing Header:"); responseData.append (meno) .append ("=") .append (hodnota) .append ("\ r \ n"); }} responseData.append ("\ r \ n"); } návrat responseData; }
3.3. Písanie odpovede
private void writeResponse (ChannelHandlerContext ctx, LastHttpContent trailer, StringBuilder responseData) {boolean keepAlive = HttpUtil.isKeepAlive (požiadavka); FullHttpResponse httpResponse = nový DefaultFullHttpResponse (HTTP_1_1, ((HttpObject) trailer) .decoderResult (). IsSuccess ()? OK: BAD_REQUEST, Unpooled.copiedBuffer (responseData.toString (), CharsetUtil )UTF) httpResponse.headers (). set (HttpHeaderNames.CONTENT_TYPE, "text / plain; charset = UTF-8"); if (keepAlive) {httpResponse.headers (). setInt (HttpHeaderNames.CONTENT_LENGTH, httpResponse.content (). readableBytes ()); httpResponse.headers (). set (HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); } ctx.write (httpResponse); if (! keepAlive) {ctx.writeAndFlush (Unpooled.EMPTY_BUFFER) .addListener (ChannelFutureListener.CLOSE); }}
4.1. ZÍSKAJTE žiadosť
zvlnenie //127.0.0.1:8080?param1=one
Parameter: PARAM1 = JEDEN Zbohom!
4.2. POST požiadavka
curl -d "ukážkový obsah" -X POST //127.0.0.1:8080
OBSAH VZORKY Ahoj!
5. Záver