HTTP-server med Netty

1. Oversikt

I denne opplæringen skal vi implementere en enkel server med overkapsling over HTTP med Netty, et asynkront rammeverk som gir oss fleksibilitet til å utvikle nettverksapplikasjoner i Java.

2. Serverstartstrapping

Før vi begynner, bør vi være klar over de grunnleggende konseptene til Netty, som kanal, handler, koder og dekoder.

Her hopper vi rett i bootstrapping av serveren, som stort sett er den samme som en enkel protokollserver:

offentlig klasse HttpServer {privat int port; privat statisk loggerlogger = LoggerFactory.getLogger (HttpServer.class); // konstruktør // hovedmetode, samme som enkel protokollserver tomromkjøring () kaster Unntak {... ServerBootstrap b = ny ServerBootstrap (); b.group (bossGroup, workerGroup) .channel (NioServerSocketChannel.class) .handler (new LoggingHandler (LogLevel.INFO)) .childHandler (new ChannelInitializer () {@Override protected void initChannel (SocketChannel ch) kaster Unntak {ChannelPipeline p = ch .pipeline (); p.addLast (ny HttpRequestDecoder ()); p.addLast (ny HttpResponseEncoder ()); p.addLast (ny CustomHttpServerHandler ());}}); ...}} 

Så her bare den childHandler er forskjellig i henhold til protokollen vi vil implementere, som er HTTP for oss.

Vi legger til tre håndterere i serverens rørledning:

  1. Netty's HttpResponseEncoder - for serialisering
  2. Netty's HttpRequestDecoder - for deserialisering
  3. Vår egen CustomHttpServerHandler - for å definere serverens oppførsel

La oss se på den siste behandleren i detalj neste.

3. CustomHttpServerHandler

Vår tilpassede behandlers jobb er å behandle innkommende data og sende et svar.

La oss bryte det ned for å forstå hvordan det fungerer.

3.1. Strukturen til håndtereren

CustomHttpServerHandler utvider Netts abstrakt SimpleChannelInboundHandler og implementerer livssyklusmetodene:

offentlig klasse CustomHttpServerHandler utvider SimpleChannelInboundHandler {private HttpRequest forespørsel; StringBuilder responseData = ny StringBuilder (); @ Overstyr offentlig ugyldig channelReadComplete (ChannelHandlerContext ctx) {ctx.flush (); } @ Override-beskyttet ugyldig channelRead0 (ChannelHandlerContext ctx, Object msg) {// implementering som skal følges} @ Override public void exceptionCaught (ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace (); ctx.close (); }}

Som metodenavnet antyder, channelReadComplete skyller handler-konteksten etter at den siste meldingen i kanalen er fortært, slik at den er tilgjengelig for neste innkommende melding. Metoden unntak Fanget er for håndtering av eventuelle unntak.

Så langt er alt vi har sett kjeleplaten.

La oss nå fortsette med de interessante tingene, implementeringen av channelRead0.

3.2. Lesing av kanalen

Brukssaken vår er enkel, serveren vil ganske enkelt forvandle forespørselens brødtekst og spørsmålsparametere til store bokstaver. Et ord med forsiktighet her om å reflektere forespørselsdata i svaret - vi gjør dette bare for demonstrasjonsformål, for å forstå hvordan vi kan bruke Netty til å implementere en HTTP-server.

Her, vi vil konsumere meldingen eller forespørselen, og sette opp svaret som anbefalt av protokollen (noter det RequestUtils er noe vi vil skrive om et øyeblikk):

if (msg instanceof HttpRequest) {HttpRequest request = this.request = (HttpRequest) msg; hvis (HttpUtil.is100ContinueExpected (forespørsel)) {writeResponse (ctx); } responsData.setLength (0); responseData.append (RequestUtils.formatParams (forespørsel)); } responseData.append (RequestUtils.evaluateDecoderResult (forespørsel)); hvis (msg forekomst av HttpContent) {HttpContent httpContent = (HttpContent) msg; responseData.append (RequestUtils.formatBody (httpContent)); responseData.append (RequestUtils.evaluateDecoderResult (forespørsel)); if (msg instanceof LastHttpContent) {LastHttpContent trailer = (LastHttpContent) msg; responseData.append (RequestUtils.prepareLastResponse (forespørsel, trailer)); writeResponse (ctx, trailer, responseData); }} 

Som vi kan se, når kanalen vår mottar en HttpForespørsel, sjekker den først om forespørselen forventer en 100 Fortsett-status. I så fall skriver vi umiddelbart tilbake med et tomt svar med status på FORTSETTE:

privat ugyldig writeResponse (ChannelHandlerContext ctx) {FullHttpResponse respons = new DefaultFullHttpResponse (HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write (respons); }

Etter det initialiserer handleren en streng som skal sendes som et svar, og legger til forespørselens parametere for den som skal sendes tilbake som den er.

La oss nå definere metoden formatParams og plasser den i en RequestUtils hjelperklasse for å gjøre det:

StringBuilder formatParams (HttpRequest-forespørsel) {StringBuilder responseData = new StringBuilder (); QueryStringDecoder queryStringDecoder = ny QueryStringDecoder (request.uri ()); Kart params = queryStringDecoder.parameters (); hvis (! params.isEmpty ()) {for (Entry p: params.entrySet ()) {Strengnøkkel = p.getKey (); Liste 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; }

Neste, ved å motta en Http Innhold, vi tar anmodningsorganet og konverterer det til store bokstaver:

StringBuilder formatBody (HttpContent httpContent) {StringBuilder responseData = new StringBuilder (); ByteBuf innhold = httpContent.content (); hvis (content.isReadable ()) {responsData.append (content.toString (CharsetUtil.UTF_8) .toUpperCase ()) .append ("\ r \ n"); } return responseData; }

Også hvis mottatt Http Innhold er en LastHttpContent, legger vi til en farvelmelding og etterfølgende overskrifter, hvis noen:

StringBuilder prepareLastResponse (HttpRequest-forespørsel, LastHttpContent-trailer) {StringBuilder responseData = ny StringBuilder (); responseData.append ("Farvel! \ r \ n"); if (! trailer.trailingHeaders (). isEmpty ()) {responseData.append ("\ r \ n"); for (CharSequence navn: trailer.trailingHeaders (). navn ()) {for (CharSequence verdi: trailer.trailingHeaders (). getAll (navn)) {responsData.append ("P.S. Trailing Header:"); responsData.append (navn) .append ("=") .append (verdi) .append ("\ r \ n"); }} responseData.append ("\ r \ n"); } return responseData; }

3.3. Skrive svaret

Nå som dataene som skal sendes er klare, kan vi skrive svaret til ChannelHandlerContext:

private void writeResponse (ChannelHandlerContext ctx, LastHttpContent trailer, StringBuilder responseData) {boolean keepAlive = HttpUtil.isKeepAlive (forespørsel); FullHttpResponse httpResponse = ny DefaultFullHttpResponse (HTTP_1_1, ((HttpObject) trailer). DekoderResultat (). ErSuccess ()? OK: BAD_REQUEST, Unpooled.copiedBuffer (responseData.toString (), CharsetUtil.UTF_8); httpResponse.headers (). sett (HttpHeaderNames.CONTENT_TYPE, "text / plain; charset = UTF-8"); hvis (keepAlive) {httpResponse.headers (). setInt (HttpHeaderNames.CONTENT_LENGTH, httpResponse.content (). readableBytes ()); httpResponse.headers (). sett (HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); } ctx.write (httpResponse); hvis (! keepAlive) {ctx.writeAndFlush (Unpooled.EMPTY_BUFFER) .addListener (ChannelFutureListener.CLOSE); }}

I denne metoden opprettet vi en FullHttpResponse med HTTP / 1.1-versjon, og legger til dataene vi hadde utarbeidet tidligere.

Hvis en forespørsel skal holdes i live, eller med andre ord, hvis forbindelsen ikke skal stenges, setter vi svarets forbindelse topptekst som holde i live. Ellers lukker vi forbindelsen.

4. Testing av serveren

For å teste serveren vår, la oss sende noen cURL-kommandoer og se på svarene.

Selvfølgelig, vi må starte serveren ved å kjøre klassen HttpServer før dette.

4.1. FÅ forespørsel

La oss først påkalle serveren og gi en informasjonskapsel med forespørselen:

krølle //127.0.0.1:8080?param1=one

Som svar får vi:

Parameter: PARAM1 = EN farvel! 

Vi kan også slå //127.0.0.1:8080?param1=one fra hvilken som helst nettleser for å se det samme resultatet.

4.2. POST-forespørsel

Som vår andre test, la oss sende en POST med kropp prøveinnhold:

krølle -d "prøveinnhold" -X POST //127.0.0.1:8080

Her er svaret:

PRØVEINNHOLD Farvel!

Denne gangen, siden forespørselen vår inneholdt et organ, serveren sendte den tilbake med store bokstaver.

5. Konklusjon

I denne opplæringen så vi hvordan vi implementerte HTTP-protokollen, spesielt en HTTP-server ved hjelp av Netty.

HTTP / 2 i Netty demonstrerer en klient-server-implementering av HTTP / 2-protokollen.

Som alltid er kildekoden tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found