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:
- Netty's HttpResponseEncoder - for serialisering
- Netty's HttpRequestDecoder - for deserialisering
- 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: 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. 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): 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: 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: Neste, ved å motta en Http Innhold, vi tar anmodningsorganet og konverterer det til store bokstaver: Også hvis mottatt Http Innhold er en LastHttpContent, legger vi til en farvelmelding og etterfølgende overskrifter, hvis noen: Nå som dataene som skal sendes er klare, kan vi skrive svaret til ChannelHandlerContext: 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. 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. La oss først påkalle serveren og gi en informasjonskapsel med forespørselen: Som svar får vi: Vi kan også slå //127.0.0.1:8080?param1=one fra hvilken som helst nettleser for å se det samme resultatet. Som vår andre test, la oss sende en POST med kropp prøveinnhold: Her er svaret: Denne gangen, siden forespørselen vår inneholdt et organ, serveren sendte den tilbake med store bokstaver. 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.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 (); }}
3.2. Lesing av kanalen
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); }}
privat ugyldig writeResponse (ChannelHandlerContext ctx) {FullHttpResponse respons = new DefaultFullHttpResponse (HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write (respons); }
StringBuilder formatParams (HttpRequest-forespørsel) {StringBuilder responseData = new StringBuilder (); QueryStringDecoder queryStringDecoder = ny QueryStringDecoder (request.uri ()); Kart
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; }
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
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); }}
4.1. FÅ forespørsel
krølle //127.0.0.1:8080?param1=one
Parameter: PARAM1 = EN farvel!
4.2. POST-forespørsel
krølle -d "prøveinnhold" -X POST //127.0.0.1:8080
PRØVEINNHOLD Farvel!
5. Konklusjon