שרת HTTP עם Netty

1. סקירה כללית

במדריך זה אנו הולכים ליישם שרת פשוט עם מעטפת עליונה HTTP עם Netty, מסגרת אסינכרונית המעניקה לנו את הגמישות לפתח יישומי רשת בג'אווה.

2. Bootstrapping בשרת

לפני שנתחיל, עלינו להיות מודעים למושגי היסוד של Netty, כגון ערוץ, מטפל, מקודד ומפענח.

כאן נקפוץ ישר לאתחול השרת, שהוא לרוב זהה לשרת פרוטוקולים פשוט:

מחלקה ציבורית HttpServer {יציאת פרטי פרטית; לוגר לוגר סטטי פרטי = LoggerFactory.getLogger (HttpServer.class); // קונסטרוקטור // שיטה עיקרית, כמו שרת פרוטוקול פשוט ריק ריק () זורק חריג {... ServerBootstrap b = ServerBootstrap חדש (); b.group (bossGroup, workerGroup) .channel (NioServerSocketChannel.class) .handler (LoggingHandler new (LogLevel.INFO)) .childHandler (ChannelInitializer חדש () {@Override מוגן בטל initChannel (SocketChannel ch) זורק חריג {ChannelPipeline p = ch .pipeline (); p.addLast (HttpRequestDecoder חדש ()); p.addLast (HttpResponseEncoder חדש ()); p.addLast (CustomHttpServerHandler חדש ());}}); ...}} 

אז כאן רק ה childHandler שונה בהתאם לפרוטוקול שאנו רוצים ליישם, שהוא HTTP עבורנו.

אנו מוסיפים לצינור השרת שלושה מטפלים:

  1. של נטי HttpResponseEncoder - לסידור
  2. של נטי HttpRequestDecoder - לעריקת ייעול
  3. שלנו CustomHttpServerHandler - להגדרת התנהגות השרת שלנו

בואו נסתכל בפירוט על המטפל האחרון בהמשך.

3. CustomHttpServerHandler

תפקיד המטפל המותאם אישית שלנו הוא לעבד נתונים נכנסים ולשלוח תגובה.

בואו נשבר את זה כדי להבין את פעולתו.

3.1. מבנה המטפל

CustomHttpServerHandler מרחיב את התקציר של נטי SimpleChannelInboundHandler ומיישם את שיטות מחזור החיים שלה:

מחלקה ציבורית CustomHttpServerHandler מרחיב את SimpleChannelInboundHandler {בקשה HttpRequest פרטית; StringBuilder responseData = StringBuilder חדש (); @ Override public void channelReadComplete (ChannelHandlerContext ctx) {ctx.flush (); } @Override מוגן חלל channelRead0 (ChannelHandlerContext ctx, Object msg) {// יישום לעקוב} @Override public void exceptionCaught (ChannelHandlerContext ctx, Throwable סיבה) {cause.printStackTrace (); ctx.close (); }}

כשם השיטה מרמז, channelReadComplete שוטף את הקשר המטפל לאחר שההודעה האחרונה בערוץ נצרכה כך שהיא תהיה זמינה להודעה הנכנסת הבאה. השיטה יוצא מן הכלל מיועד לטיפול בחריגים אם בכלל.

עד כה, כל מה שראינו הוא קוד ה- boilerplate.

עכשיו בואו נמשיך עם הדברים המעניינים, היישום של channelRead0.

3.2. קורא את הערוץ

מקרה השימוש שלנו פשוט, השרת פשוט יהפוך את גוף הבקשה ופרמטרי השאילתה, אם בכלל, לרישיות. מילת אזהרה כאן לגבי שיקוף נתוני בקשה בתגובה - אנו עושים זאת רק למטרות הדגמה, כדי להבין כיצד אנו יכולים להשתמש ב- Netty כדי להטמיע שרת HTTP.

פה, אנו נצרוך את ההודעה או הבקשה ונגדיר את תגובתה בהתאם להמלצת הפרוטוקול (ציין זאת RequestUtils הוא משהו שנכתוב רק בעוד רגע):

אם (msg מופע של HttpRequest) {HttpRequest בקשה = this.request = (HttpRequest) msg; אם (HttpUtil.is100ContinueExpected (בקשה)) {writeResponse (ctx); } responseData.setLength (0); responseData.append (RequestUtils.formatParams (בקשה)); } responseData.append (RequestUtils.evaluateDecoderResult (בקשה)); אם (msg מופע של HttpContent) {HttpContent httpContent = (HttpContent) msg; responseData.append (RequestUtils.formatBody (httpContent)); responseData.append (RequestUtils.evaluateDecoderResult (בקשה)); if (msg instanceof LastHttpContent) {טריילר LastHttpContent = (LastHttpContent) msg; responseData.append (RequestUtils.prepareLastResponse (בקשה, טריילר)); writeResponse (ctx, trailer, responseData); }} 

כפי שאנו רואים, כאשר הערוץ שלנו מקבל HttpRequest, היא בודקת תחילה אם הבקשה מצפה לסטטוס 100 המשך. במקרה זה, אנו מיד כותבים חזרה עם תגובה ריקה עם סטטוס לְהַמשִׁיך:

חלל פרטי writeResponse (ChannelHandlerContext ctx) {FullHttpResponse response = new DefaultFullHttpResponse (HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER); ctx.write (תגובה); }

לאחר מכן, המטפל מאותחל מחרוזת שתישלח כתגובה ומוסיף אליה את פרמטרי השאילתה של הבקשה כדי להישלח בחזרה כמות שהיא.

בואו נגדיר את השיטה formatParams והניחו אותו ב- RequestUtils כיתת עוזר לעשות זאת:

FormatParams StringBuilder (בקשת HttpRequest) {StringBuilder responseData = StringBuilder חדש (); QueryStringDecoder queryStringDecoder = QueryStringDecoder חדש (request.uri ()); מַפָּה params = queryStringDecoder.parameters (); אם (! params.isEmpty ()) {עבור (כניסה p: params.entrySet ()) {מפתח מחרוזת = p.getKey (); רשימת vals = p.getValue (); עבור (String val: vals) {responseData.append ("Parameter:") .append (key.toUpperCase ()). append ("="). append (val.toUpperCase ()). append ("\ r \ n "); }} responseData.append ("\ r \ n"); } return responseData; }

לאחר מכן, בקבלת Http תוכן, אנו לוקחים את גוף הבקשה וממירים אותו לאותיות רישיות:

פורמט StringBuilderBody (HttpContent httpContent) {StringBuilder responseData = StringBuilder חדש (); תוכן ByteBuf = httpContent.content (); אם (content.isReadable ()) {responseData.append (content.toString (CharsetUtil.UTF_8) .toUpperCase ()) .append ("\ r \ n"); } return responseData; }

כמו כן, אם קיבל Http תוכן הוא LastHttpContent, אנו מוסיפים הודעת פרידה וכותרות נגררות, אם יש:

StringBuilder prepareLastResponse (בקשת HttpRequest, טריילר LastHttpContent) {StringBuilder responseData = StringBuilder חדש (); responseData.append ("להתראות! \ r \ n"); אם (! trailer.trailingHeaders (). isEmpty ()) {responseData.append ("\ r \ n"); עבור (שם CharSequence: trailer.trailingHeaders (). שמות ()) {עבור (ערך CharSequence: trailer.trailingHeaders (). getAll (שם)) {responseData.append ("כותרת נגררת P.S.:"); responseData.append (name) .append ("=") .append (value) .append ("\ r \ n"); }} responseData.append ("\ r \ n"); } return responseData; }

3.3. כתיבת התגובה

עכשיו שהנתונים שלנו שישלחו מוכנים, נוכל לכתוב את התגובה ל- ChannelHandlerContext:

חלל פרטי writeResponse (ChannelHandlerContext ctx, טריילר LastHttpContent, תגובה StringBuilder) {boolean keepAlive = HttpUtil.isKeepAlive (בקשה); FullHttpResponse httpResponse = DefaultFullHttpResponse חדש (HTTP_1_1, ((HttpObject) טריילר). DecoderResult (). IsSuccess ()? בסדר: BAD_REQUEST, Unpooled.copiedBuffer (responseData.toString (), CharsetUtil.UTF_8); httpResponse.headers (). set (HttpHeaderNames.CONTENT_TYPE, "text / plain; charset = UTF-8"); אם (keepAlive) {httpResponse.headers (). setInt (HttpHeaderNames.CONTENT_LENGTH, httpResponse.content (). readableBytes ()); httpResponse.headers (). set (HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); } ctx.write (httpResponse); אם (! keepAlive) {ctx.writeAndFlush (Unpooled.EMPTY_BUFFER) .addListener (ChannelFutureListener.CLOSE); }}

בשיטה זו יצרנו a FullHttpResponse עם גרסת HTTP / 1.1, הוספת הנתונים שהכנו קודם.

אם יש להשאיר בקשה בחיים, או במילים אחרות, אם הקשר לא אמור להיסגר, הגדרנו את התגובה חיבור כותרת כ להשאיר בחיים. אחרת, אנו סוגרים את הקשר.

4. בדיקת השרת

כדי לבדוק את השרת שלנו, בוא נשלח כמה פקודות cURL ונראה את התגובות.

כמובן, עלינו להפעיל את השרת על ידי הפעלת הכיתה HttpServer לפני זה.

4.1. קבל בקשה

בואו נפעיל תחילה את השרת ונספק קובץ cookie עם הבקשה:

תלתל //127.0.0.1:8080?param1=one

כתגובה, אנו מקבלים:

פרמטר: PARAM1 = ONE Good Bye! 

אנחנו יכולים גם להכות //127.0.0.1:8080?param1=one מכל דפדפן כדי לראות את אותה התוצאה.

4.2. בקשת POST

כמבחן השני שלנו, בואו נשלח POST עם גוף תוכן לדוגמא:

תלתל -ד "תוכן לדוגמא" -X POST //127.0.0.1:8080

הנה התגובה:

תוכן לדוגמא להתראות!

הפעם, מכיוון שבקשתנו הכילה גוף, השרת שלח אותו חזרה באותיות רישיות.

5. מסקנה

במדריך זה ראינו כיצד ליישם את פרוטוקול HTTP, במיוחד שרת HTTP המשתמש ב- Netty.

HTTP / 2 ב- Netty מדגים יישום שרת לקוח של פרוטוקול HTTP / 2.

כמו תמיד, קוד המקור זמין ב- GitHub.


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