HTTP poslužitelj s Nettyjem

1. Pregled

U ovom uputstvu idemo implementirati jednostavan poslužitelj gornjeg slova preko HTTP s Nettyjem, asinkroni okvir koji nam daje fleksibilnost za razvoj mrežnih aplikacija u Javi.

2. Pokretanje sustava poslužitelja

Prije nego što započnemo, trebali bismo biti svjesni osnovnih pojmova Nettyja, poput kanala, rukovatelja, kodera i dekodera.

Ovdje ćemo prijeći izravno na bootstrapping poslužitelj, koji je uglavnom isti kao i jednostavni protokolni poslužitelj:

javna klasa HttpServer {private int port; privatni statički logger logger = LoggerFactory.getLogger (HttpServer.class); // konstruktor // glavna metoda, isto kao i jednostavni protokol poslužitelj public void run () baca iznimku {... ServerBootstrap b = new ServerBootstrap (); b.group (bossGroup, workerGroup) .channel (NioServerSocketChannel.class) .handler (new LoggingHandler (LogLevel.INFO)) .childHandler (novi ChannelInitializer () {@Override protected void initChannel (SocketChannel ch) throws Excep p .pipeline (); p.addLast (novi HttpRequestDecoder ()); p.addLast (novi HttpResponseEncoder ()); p.addLast (novi CustomHttpServerHandler ());}}); ...}} 

Dakle, ovdje samo dijete Rukovatelja razlikuje se prema protokolu koji želimo implementirati, što je za nas HTTP.

U cjevovod poslužitelja dodajemo tri obrađivača:

  1. Netty's HttpResponseEncoder - za serializaciju
  2. Netty's HttpRequestDecoder - za deserializaciju
  3. Naša vlastita CustomHttpServerHandler - za definiranje ponašanja našeg poslužitelja

Pogledajmo sljedeći detaljno posljednjeg rukovatelja.

3. CustomHttpServerHandler

Posao našeg prilagođenog rukovatelja je obrada ulaznih podataka i slanje odgovora.

Razdvojimo ga kako bismo razumjeli njegovo djelovanje.

3.1. Struktura rukovatelja

CustomHttpServerHandler proširuje Nettyjev sažetak SimpleChannelInboundHandler i primjenjuje svoje metode životnog ciklusa:

javna klasa CustomHttpServerHandler proširuje SimpleChannelInboundHandler {privatni zahtjev HttpRequest; StringBuilder responseData = novi StringBuilder (); @Override public void channelReadComplete (ChannelHandlerContext ctx) {ctx.flush (); } @Override protected void channelRead0 (ChannelHandlerContext ctx, Object msg) {// implementacija koja slijedi} @Override public void exceptionCaught (ChannelHandlerContext ctx, Throwable Uzrok) {uzrok.printStackTrace (); ctx.close (); }}

Kao što naziv metode sugerira, channelReadComplete ispire kontekst rukovatelja nakon što je potrošena zadnja poruka na kanalu tako da je dostupna za sljedeću dolaznu poruku. Metoda iznimkaUhvaćen je za rukovanje iznimkama ako postoje.

Do sada smo vidjeli samo šifru uzorka.

Ajmo sada sa zanimljivim stvarima, provedbom channelRead0.

3.2. Čitajući Kanal

Naš je slučaj upotrebe jednostavan, poslužitelj će jednostavno transformirati tijelo zahtjeva i parametre upita, ako postoje, u velika slova. Riječ opreza ovdje pri odražavanju podataka zahtjeva u odgovoru - to činimo samo u svrhu demonstracije kako bismo razumjeli kako Netty možemo koristiti za implementaciju HTTP poslužitelja.

Ovdje, potrošit ćemo poruku ili zahtjev i postaviti odgovor prema preporukama protokola (imajte na umu da RequestUtils je nešto što ćemo napisati za trenutak):

if (msg instanceof HttpRequest) {HttpRequest request = this.request = (HttpRequest) msg; if (HttpUtil.is100ContinueExpected (zahtjev)) {writeResponse (ctx); } responseData.setLength (0); responseData.append (RequestUtils.formatParams (zahtjev)); } responseData.append (RequestUtils.evaluateDecoderResult (zahtjev)); if (msg instanceof HttpContent) {HttpContent httpContent = (HttpContent) msg; responseData.append (RequestUtils.formatBody (httpContent)); responseData.append (RequestUtils.evaluateDecoderResult (zahtjev)); if (msg instanceof LastHttpContent) {LastHttpContent trailer = (LastHttpContent) msg; responseData.append (RequestUtils.prepareLastResponse (zahtjev, prikolica)); writeResponse (ctx, trailer, responseData); }} 

Kao što vidimo, kada naš kanal primi HttpRequest, prvo provjerava očekuje li zahtjev status 100 Continue. U tom slučaju, odmah vam pišemo prazan odgovor sa statusom NASTAVITI:

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

Nakon toga, voditelj inicijalizira niz koji će se poslati kao odgovor i dodaje parametre upita zahtjeva da bi se vratio onakav kakav jest.

Ajmo sada definirati metodu formatParams i smjestite ga u a RequestUtils pomoćna klasa za to:

StringBuilder formatParams (zahtjev za HttpRequest) {StringBuilder responseData = novi StringBuilder (); QueryStringDecoder queryStringDecoder = novi QueryStringDecoder (request.uri ()); Karta params = queryStringDecoder.parameters (); if (! params.isEmpty ()) {for (Unos p: params.entrySet ()) {Ključ niza = p.getKey (); Popis vals = p.getValue (); za (Niz val: vals) {responseData.append ("Parametar:") .append (key.toUpperCase ()). append ("=") .append (val.toUpperCase ()). append ("\ r \ n "); }} responseData.append ("\ r \ n"); } return responseData; }

Dalje, po primanju HttpContent, uzimamo tijelo zahtjeva i pretvaramo ga u velika slova:

StringBuilder formatBody (HttpContent httpContent) {StringBuilder responseData = novi StringBuilder (); Sadržaj ByteBuf = httpContent.content (); if (content.isReadable ()) {responseData.append (content.toString (CharsetUtil.UTF_8) .toUpperCase ()) .append ("\ r \ n"); } return responseData; }

Također, ako je primljeno HttpContent je LastHttpContent, dodajemo poruku zbogom i zaglavlja, ako postoje:

StringBuilder pripremaLastResponse (zahtjev za HttpRequest, prikolica LastHttpContent) {StringBuilder responseData = novi StringBuilder (); responseData.append ("Zbogom! \ 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. Zaostalo zaglavlje:"); responseData.append (ime) .append ("=") .append (value) .append ("\ r \ n"); }} responseData.append ("\ r \ n"); } return responseData; }

3.3. Pisanje odgovora

Sada kada su naši podaci za slanje spremni, odgovor možemo napisati na ChannelHandlerContext:

private void writeResponse (ChannelHandlerContext ctx, prikolica LastHttpContent, StringBuilder responseData) {boolean keepAlive = HttpUtil.isKeepAlive (zahtjev); FullHttpResponse httpResponse = novi DefaultFullHttpResponse (HTTP_1_1, ((HttpObject) prikolica) .decoderResult (). IsSuccess ()? OK: BAD_REQUEST, Unpooled.copiedBuffer (responseData.toString (), Charset8) 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); }}

Ovom metodom stvorili smo a FullHttpResponse s HTTP / 1.1 verzijom, dodajući podatke koje smo ranije pripremili.

Ako zahtjev treba održavati živim, ili drugim riječima, ako se veza ne želi prekinuti, postavljamo odgovor povezanost zaglavlje kao držati na životu. U suprotnom, prekidamo vezu.

4. Testiranje poslužitelja

Da bismo testirali naš poslužitelj, pošaljite neke naredbe cURL i pogledajmo odgovore.

Naravno, trebamo pokrenuti poslužitelj izvođenjem klase HttpServer prije ovoga.

4.1. GET Zahtjev

Prvo se pozovimo na poslužitelj, uz zahtjev dajemo kolačić:

uvojak //127.0.0.1:8080?param1=one

Kao odgovor dobivamo:

Parametar: PARAM1 = JEDNO Zbogom! 

Možemo i pogoditi //127.0.0.1:8080?param1=one iz bilo kojeg preglednika da biste vidjeli isti rezultat.

4.2. POST zahtjev

Kao naš drugi test, pošaljite POST s tijelom sadržaj uzorka:

curl -d "uzorak sadržaja" -X POST //127.0.0.1:8080

Evo odgovora:

SADRŽAJ UZORKA Doviđenja!

Ovaj put, budući da je naš zahtjev sadržavao tijelo, poslužitelj ga je vratio velikim slovom.

5. Zaključak

U ovom uputstvu vidjeli smo kako implementirati HTTP protokol, posebno HTTP poslužitelj koji koristi Netty.

HTTP / 2 u Netty-u pokazuje implementaciju klijent-poslužitelja protokola HTTP / 2.

Kao i uvijek, izvorni kod dostupan je na GitHub-u.