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:
- Netty's HttpResponseEncoder - za serializaciju
- Netty's HttpRequestDecoder - za deserializaciju
- 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: 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. 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): 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: 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: Dalje, po primanju HttpContent, uzimamo tijelo zahtjeva i pretvaramo ga u velika slova: Također, ako je primljeno HttpContent je LastHttpContent, dodajemo poruku zbogom i zaglavlja, ako postoje: Sada kada su naši podaci za slanje spremni, odgovor možemo napisati na ChannelHandlerContext: 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. 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. Prvo se pozovimo na poslužitelj, uz zahtjev dajemo kolačić: Kao odgovor dobivamo: Možemo i pogoditi //127.0.0.1:8080?param1=one iz bilo kojeg preglednika da biste vidjeli isti rezultat. Kao naš drugi test, pošaljite POST s tijelom sadržaj uzorka: Evo odgovora: Ovaj put, budući da je naš zahtjev sadržavao tijelo, poslužitelj ga je vratio velikim slovom. 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.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 (); }}
3.2. Čitajući Kanal
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); }}
private void writeResponse (ChannelHandlerContext ctx) {FullHttpResponse response = new DefaultFullHttpResponse (HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write (odgovor); }
StringBuilder formatParams (zahtjev za HttpRequest) {StringBuilder responseData = novi StringBuilder (); QueryStringDecoder queryStringDecoder = novi QueryStringDecoder (request.uri ()); Karta
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; }
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
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); }}
4.1. GET Zahtjev
uvojak //127.0.0.1:8080?param1=one
Parametar: PARAM1 = JEDNO Zbogom!
4.2. POST zahtjev
curl -d "uzorak sadržaja" -X POST //127.0.0.1:8080
SADRŽAJ UZORKA Doviđenja!
5. Zaključak