HTTP-kiszolgáló Netty-vel

1. Áttekintés

Ebben az oktatóanyagban fogunk valósítson meg egy egyszerű felső burkolatú kiszolgálót HTTP Netty-vel, egy aszinkron keretrendszer, amely rugalmasságot biztosít számunkra a hálózati alkalmazások fejlesztésére a Java-ban.

2. A kiszolgáló rendszerindítása

Mielőtt elkezdenénk, tisztában kell lennünk a Netty olyan alapfogalmaival, mint a csatorna, a kezelő, a kódoló és a dekóder.

Itt egyenesen a szerver bootstrapelésére ugrunk, amely többnyire megegyezik egy egyszerű protokoll-szerverrel:

public class HttpServer {private int port; privát statikus naplózó naplózó = LoggerFactory.getLogger (HttpServer.class); // konstruktor // fő módszer, ugyanaz, mint az egyszerű protokoll-kiszolgáló public void run () dobja a Kivételt {... ServerBootstrap b = new ServerBootstrap (); b.group (bossGroup, workerGroup) .channel (NioServerSocketChannel.class) .handler (új LoggingHandler (LogLevel.INFO)) .childHandler (új ChannelInitializer () {@Override protected void initChannel (SocketChannel {ChannelPlay ch) dobások .pipeline (); p.addLast (új HttpRequestDecoder ()); p.addLast (új HttpResponseEncoder ()); p.addLast (új CustomHttpServerHandler ());}}); ...}} 

Ezért itt csak a childHandler különbözik a megvalósítani kívánt protokolltól, ami HTTP számunkra.

Három kezelőt adunk a szerver folyamatához:

  1. Netty's HttpResponseEncoder - sorosításhoz
  2. Netty's HttpRequestDecoder - deserializációhoz
  3. A sajátunk CustomHttpServerHandler - a szerver viselkedésének meghatározásához

Most nézzük meg részletesen az utolsó kezelőt.

3. CustomHttpServerHandler

Egyéni kezelőnk feladata a bejövő adatok feldolgozása és válasz küldése.

Bontjuk le, hogy megértsük a működését.

3.1. A kezelő felépítése

CustomHttpServerHandler kiterjeszti Netty absztraktját SimpleChannelInboundHandler és végrehajtja életciklus-módszereit:

public class CustomHttpServerHandler kiterjeszti a SimpleChannelInboundHandler {private HttpRequest kérést; StringBuilder responseData = új StringBuilder (); @Orride public void channelReadComplete (ChannelHandlerContext ctx) {ctx.flush (); } @Orride protected void channelRead0 (ChannelHandlerContext ctx, Object msg) {// követendő megvalósítás} @Orride public void kivételCaught (ChannelHandlerContext ctx, Throwable reason) {caus.printStackTrace (); ctx.close (); }}

Ahogy a módszer neve is sugallja, channelReadComplete a csatorna utolsó üzenetének elfogyasztása után kiüríti a kezelő környezetet, hogy elérhető legyen a következő bejövő üzenethez. A módszer, a metódus kivételFogott az esetleges kivételek kezelésére szolgál.

Eddig csak a kazán kódot láttuk.

Most folytassuk az érdekes dolgokkal, a megvalósítással channelRead0.

3.2. A Csatorna olvasása

Felhasználási esetünk egyszerű, a szerver egyszerűen átalakítja a kérelem törzsét és a lekérdezési paramétereket, ha vannak, nagybetűvé. Vigyázat: a kérelem adatait a válaszban tükrözzük - ezt csak demonstrációs célokra tesszük, hogy megértsük, hogyan használhatjuk a Netty-t egy HTTP szerver megvalósítására.

Itt, felhasználjuk az üzenetet vagy a kérést, és a protokoll ajánlása szerint állítjuk be a válaszát (vegye figyelembe, hogy RequestUtils pillanatok alatt írunk):

if (HttpRequest msg példánya) {HttpRequest kérés = this.request = (HttpRequest) msg; if (HttpUtil.is100ContinueExpected (kérés)) {writeResponse (ctx); } responseData.setLength (0); responseData.append (RequestUtils.formatParams (kérés)); } responseData.append (RequestUtils.evaluateDecoderResult (kérés)); if (HttpContent msg példánya) {HttpContent httpContent = (HttpContent) msg; responseData.append (RequestUtils.formatBody (httpContent)); responseData.append (RequestUtils.evaluateDecoderResult (kérés)); if (LastHttpContent msg példánya) {LastHttpContent trailer = (LastHttpContent) msg; responseData.append (RequestUtils.prepareLastResponse (kérés, trailer)); writeResponse (ctx, trailer, responseData); }} 

Mint láthatjuk, amikor csatornánk kap egy HttpRequest, először ellenőrzi, hogy a kérés 100 Folytatás állapotot vár-e. Ebben az esetben azonnal visszaírunk egy üres státusszal FOLYTATNI:

private void writeResponse (ChannelHandlerContext ctx) {FullHttpResponse response = new DefaultFullHttpResponse (HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write (válasz); }

Ezt követően a kezelő inicializálja a válaszként küldendő karakterláncot, és hozzáadja a kérés lekérdezési paramétereit ahhoz, hogy visszaküldhető legyen.

Most definiáljuk a módszert formatParams és helyezze a RequestUtils segítő osztály erre:

StringBuilder formatParams (HttpRequest kérés) {StringBuilder responseData = új StringBuilder (); QueryStringDecoder queryStringDecoder = új QueryStringDecoder (request.uri ()); Térkép params = queryStringDecoder.parameters (); if (! params.isEmpty ()) {for (Bejegyzés p: params.entrySet ()) {String kulcs = p.getKey (); List vals = p.getValue (); for (String val: vals) {responseData.append ("Parameter:") .append (key.toUpperCase ()). append ("=") .append (val.toUpperCase ()). append ("\ r \ n "); }} responseData.append ("\ r \ n"); } return responseData; }

Ezután egy HttpContent, átvesszük a kérelem törzsét, és átalakítjuk nagybetűvé:

StringBuilder formatBody (HttpContent httpContent) {StringBuilder responseData = új StringBuilder (); ByteBuf tartalom = httpContent.content (); if (content.isReadable ()) {responseData.append (content.toString (CharsetUtil.UTF_8) .toUpperCase ()) .append ("\ r \ n"); } return responseData; }

Továbbá, ha a kapott HttpContent egy LastHttpContent, hozzáadunk egy búcsú üzenetet és a záró fejléceket, ha vannak:

StringBuilder PreparLastResponse (HttpRequest kérés, LastHttpContent trailer) {StringBuilder responseData = új StringBuilder (); responseData.append ("Viszlát! \ r \ n"); if (! trailer.trailingHeaders (). isEmpty ()) {responseData.append ("\ r \ n"); for (CharSequence neve: trailer.trailingHeaders (). names ()) {for (CharSequence értéke: trailer.trailingHeaders (). getAll (név)) {responseData.append ("P.S. Trailing Header:"); responseData.append (név) .append ("=") .append (érték) .append ("\ r \ n"); }} responseData.append ("\ r \ n"); } return responseData; }

3.3. A válasz megírása

Most, hogy az elküldendő adataink készen állnak, meg tudjuk írni a választ a ChannelHandlerContext:

private void writeResponse (ChannelHandlerContext ctx, LastHttpContent trailer, StringBuilder responseData) {boolean keepAlive = HttpUtil.isKeepAlive (kérés); FullHttpResponse httpResponse = new DefaultFullHttpResponse (HTTP_1_1, ((HttpObject) trailer) .decoderResult (). IsSuccess ()? OK: BAD_REQUEST, Unpooled.copiedBuffer (responseData.toString (), CharsetUtil.UT 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); }}

Ezzel a módszerrel hoztuk létre a FullHttpResponse HTTP / 1.1 verzióval, hozzáadva azokat az adatokat, amelyeket korábban készítettünk.

Ha egy kérést életben akarunk tartani, vagy más szavakkal, ha a kapcsolatot nem kell lezárni, akkor a válasz kapcsolat fejléc as életben tartani. Ellenkező esetben lezárjuk a kapcsolatot.

4. A szerver tesztelése

A szerver teszteléséhez küldjünk néhány cURL parancsot, és nézzük meg a válaszokat.

Természetesen, az osztály futtatásával kell elindítanunk a szervert HttpServer ezt megelőzően.

4.1. GET kérés

Először hívjuk meg a szervert, és egy cookie-t adunk a kéréshez:

curl //127.0.0.1:8080?param1=one

Válaszként a következőket kapjuk:

Paraméter: PARAM1 = EGY Viszlát! 

Ütni is tudunk //127.0.0.1:8080?param1=one bármely böngészőből ugyanazt az eredményt láthatja.

4.2. POST kérés

Második tesztként küldjünk POST-ot testtel mintatartalom:

curl -d "mintatartalom" -X POST //127.0.0.1:8080

Íme a válasz:

MINTATARTALOM Viszlát!

Ezúttal, mivel kérésünkben volt egy test, a szerver nagybetűvel küldte vissza.

5. Következtetés

Ebben az oktatóanyagban láttuk, hogyan lehet a HTTP-protokollt megvalósítani, különös tekintettel a HTTP-kiszolgálóra a Netty segítségével.

A Nettyben található HTTP / 2 bemutatja a HTTP / 2 protokoll kliens-szerver megvalósítását.

Mint mindig, a forráskód is elérhető a GitHubon.