Nine MVP's Blog

07/03/2014

JavaScript ECMAScript 6

Filed under: javascript, W3C — Tags: , — Nine MVP @ 10:37 am

รวบรวมหัวข้อน่าสนใจของ JavaScript ECMAScript 6 ที่กำลังจะมา 

== New Syntax ==

  • Creating Variables with Block Scope
  • Constants
  • Default Parameters
  • Rest and Spread
  • Destructuring
  • The for … of Loop
  • Iterators
  • Generators
  • Comprehensions
  • Syntax Refinements

== Organizational ==

  • Classes
  • Modules

== New stamdard types ==

  • Set
  • Map
  • WeakMap
  • Promises
  • Proxies

เรียนรู้ ES6 เบื้องต้น
https://github.com/lukehoban/es6features

ตรวจสอบ browser ของท่าน ECMAScript 6 compatibility table
http://kangax.github.io/es5-compat-table/es6/

ความก้าวหน้าในการรองรับของแต่ละ browser
FireFox
https://developer.mozilla.org/en/docs/Web/JavaScript/ECMAScript_6_support_in_Mozilla

15/10/2012

ASP.NET MVC Series: Web Push Technology II.1

Filed under: ASP.NET, ASP.NET MVC, Design Pattern, W3C, WEB — Tags: , , , , — Nine MVP @ 8:46 pm

จากตอนที่แล้ว web push technology I ได้แนะนำความเป็นมาของการทำงานแบบ push และเทคโนโลยีต่างๆที่มีให้ใช้งานกันไปแล้ว ตอนนี้เราจะมาเขียนโปรแกม โดยขอเริ่มที่การใช้งานตัว Server Sent Event (SSE) กันก่อน และจะไปจบที่ WebSockets (ws) ฮีโร่มัยซินของเราในตอนสุดท้ายของเรื่องนี้

เตรียมเครื่องมือ Tool:

  1. Visual Studio 2010+ (บทความใช้ vs2012)
  2. ASP.NET MVC 4.0
  3. HTML5 Browser (Chrome v20+, FF v14+, Opera v11+)
  4. jQuery latest version
  5. knockout.js latest version

โจทย์การทดสอบนี้คือโปรแกรม Push Mail

โจทย์ตัวอย่าง

  • เมื่อ load inbox ในครั้งแรกที่เข้าสู่ระบบ
  • ระบบสามารถ push email ไปยังผู้รับปลายทาง โดยเมื่อมีอีเมลเข้ามาใหม่ก็ส่งเข้า inbox ของ online user ทันที 
  • สามารถสร้างและส่งอีเมลไปหา user คนอื่นในระบบได้

มาถึงช่วงของการละเลงโค้ดในตอนนี้ผมขอนำเสนอการใช้งาน

 

Server Sent Event (EventSource) Solution

key technologies สิ่งที่วางไว้สร้าง SSE Solution นี้ก็จะมีดังนี้

  • Server จะเป็น ASP.NET WEB API  ในการบริการงานด้าน push mail ซึ่งส่งข้อมูลอีเมลใหม่ไปยัง user ที่ online อยู่หากมีการส่งอีเมล์เข้ามา ณ ตอนที่ออนไลน์อยู่ 
  • Client จะใช้เป็น ASP.NET MVC, jQuery, Knockout.js ในการทำงาน เข้าระบบ, บันทึกส่งเมล และแสดงผล push mail จาก server

image

จากภาพด้านบนอธิบายว่า

  1. เมื่อ user1 เข้าระบบมาแล้วก็ให้ทำการ load inbox เฉพาะทีมีคนส่งมาหากลับมาให้หน้าแรก

  2. พร้อมทั้ง subscribed เข้าใน pool ของ user online เพื่อใช้ในการ push new email

  3. return inbox data กลับไป

อาจจะ 10 นาทีต่อมามี user2  ทำการเข้าระบบมาส่งอีเมลโดย

  4. user2 เลือกส่งเมลหา user1

  5. พบ user1 ออนไลน์อยู่และทำการ push email ไปให้

 

SSE Demo Output

จำลองผู้ใช้งานขึ้น 2 คนชื่อ user1 และ user2 ตามภาพการทำงานด้านบน จะได้ผลตามลำดับภาพดังนี้

image 

เมื่อ user1 เปิด browser เข้าระบบมาครั้งแรก

image 

เมื่อ user1 ทำการใส่ username แล้วกดปุ่ม load inbox หากเข้าระบบได้ก็จะมีรายการ inbox item และสามารถส่งอีเมลได้ ซึ่งถือว่า user1 online อยู่ในระบบของ push mail แล้ว

image 

เมื่อ user2 เปิด browser เข้าระบบมาครั้งแรก และทำการใส่ username แล้วกดปุ่ม load inbox หากเข้าระบบได้ก็จะมีรายการ inbox item และสามารถส่งอีเมลได้ ซึ่งถือว่า user2 online อยู่ในระบบของ push mail แล้ว  

image

user2 ทำการส่งอีเมลหา user1 ที่ user1@nine.com ตามรูป กด send

image 

อีเมลจาก user2 ได้ถูกส่งเข้าไปในระบบเรียบร้อย

image

ที่ browser ของ user1 จะมี new email ส่งเข้า inbox ไปแสดงผลตามภาพ และมีป๊ปบอัพโผล่บอกว่าใครส่งมา

 

อธิบาย MVCPushSSE Project

มาดู flow การทำงานของตัวโปรแกรมใน mvc application จากภาพด้านล่าง (เขียนโค้ดเรียบๆไม่เล่นกระบวนท่ามาก)

image

 

* การอธิบายโค้ดจะยึดตาม flow ด้านบนและชี้โค้ดให้เห็นเป็นส่วนๆ

 

0. user access to homepage

เมื่อ user เปิด browser แล้วเข้าเว็บมาครั้งแรก http://localhost:xxx/ จะไปเข้า /Email/Index (controller/action) ตามที่ได้ตั้งค่าไว้ใน routeconfig ทำให้มีการสร้าง viewmodel ของ knockout เกิดขึ้นตามนี้

   1: <script>

   2:  

   3:     //EventSource object

   4:     var source;

   5:     

   6:     //main view model

   7:     var viewModel = {

   8:         username: ko.observable(""),//map to username input

   9:         email: ko.observable(""), //map to email input

  10:         messages: ko.observableArray([]) //dynamic array inbox message (json) output

  11:     };

  12:  

  13:     //new email object viewmodel

  14:     viewModel.newEmail = {

  15:         Username: ko.computed(function () { return viewModel.username(); }), //get username value from viewModel.username

  16:         To: ko.observable(""), //map to To input

  17:         From: ko.computed(function () { return viewModel.email(); }), //get From value from viewModel.email

  18:         Title: ko.observable(""), //map to To input

  19:         Message: ko.observable("") //map to Body input

  20:     };

และเมื่อ html document โหลดเสร็จเรียบร้อบก็จะถึงการสั่งให้ เอา viewmodel ด้านบนมา map เข้าใน knockout engine ด้วยคำสั่งใน line4

   1: $(document).ready(function () {

   2:  

   3:     //binding mapping to viewModel

   4:     ko.applyBindings(viewModel);

   5:  

 

จากนั้นเมื่อมีการเรียกเข้าไปที่ EmailController –> Index Action จะมี code ทำงานดังนี้

line4: สร้างตัวแปร efcontext ไว้ใช้งาน
line7-10: สร้าง action สำหรับไว้ดึง view ไปแสดงผลครั้งแรกที่เข้ามาโดยจะ return Index.cshtml ออกไป

   1: public class EmailController : Controller

   2: {

   3:  

   4:     MailBoxDBContext mbctx = new MailBoxDBContext();

   5:  

   6:     // GET: /Email/

   7:     public ActionResult Index()

   8:     {

   9:         return View();

  10:     }

 

1. เมื่อ user กด load mailbox

เมื่อ user ได้ใส่ username และกดส่งกลับเข้ามา javascript ใน index.cshtml ส่วนนี้จะทำงานตามที่ได้ hook event เอาไว้ โดยสั่ง GET username ส่งไปที่ /Email/GetMyInbox  ซึ่งจะไปโหลด user inbox  กลับออกมา

   1: $('#loadMailBox').live("click", function (e) {

   2:     viewModel.email('');

   3:     var username = $('#username').val();

   4:     $.getJSON("/Email/GetMyInbox", { username: viewModel.username() },

   5:         function (data) {

   6:             if (!data.success) {

   7:                 if (source != null) source.close();

   8:                 alert(data.msg);

   9:             } else {

  10:                 viewModel.email(data.email);

  11:                 InitialSSE();

  12:                 $('#inbox').empty();

  13:                 viewModel.messages(data.mails);

  14:             }

  15:         }

  16:     );

  17: });

  1. line4: เป็นการสั่ง getJSON() มี url, json object โดยดึงเอา viewModel.username ไปใช้  และทำการยิงไป “/Email/GetMyInbox”
  2. line10: ก็จะเอา json ที่ได้ส่ง email เข้าไปใน ko เพื่อไปแสดงผลใน input email
  3. line11: จากนั้นสั่งให้ setup ตัว SSE object ด้วย InitialSSE(); //ไว้อธิบายต่อตอนหลัง
  4. line12: จากนั้นก็สั่ง clear ทุกอย่างใน inbox
  5. line13: และส่ง item ที่โหลดมาจากได้ inbox ของ user ส่งไป binding ผ่าน viewModel.messages

 

2. การทำงานของ GetMyInbox Action 

หลังจากที่ GET มาเข้าที่ EmailController –> GetMyInbox () Action ซึ่งเป็นการจำลองว่า user login เข้ามาก็จะได้ inbox ของตัวเองกลับไป

   1: [HttpGet]

   2: public JsonResult GetMyInbox(string username)

   3: {

   4:     try

   5:     {

   6:         var user = mbctx.Users.SingleOrDefault(o => o.UserName == username);

   7:         //not found user

   8:         if (user == null)

   9:             return Json(new { success = false, msg = string.Format("not found user {0}", username) },

  10:                         JsonRequestBehavior.AllowGet);

  11:  

  12:         //get mailbox send to this users email

  13:         var mails = mbctx.MailMessages.Where(o => o.To.ToLower().Trim() == user.Email.ToLower().Trim())

  14:             .OrderByDescending(o => o.ReceivedDate).ToList()

  15:             .Select(o => new MailMessageView

  16:                     {

  17:                         Id = o.Id,

  18:                         To = o.To,

  19:                         From = o.From,

  20:                         ReceivedDate = o.ReceivedDate.ToString("dd/MM/yyyy HH:mm:ss"),

  21:                         Title = o.Title

  22:                     }).ToList();

  23:  

  24:         //return json to client

  25:         return Json(new { success = true, email = user.Email, mails = mails }, JsonRequestBehavior.AllowGet);

  26:  

  27:     }

  28:     catch (Exception ex)

  29:     {

  30:         return Json(new { success = false, msg = ex.Message }, JsonRequestBehavior.AllowGet);

  31:     }

  32: }

  1. line15-45: ดึงข้อมูลอีเมลทั้งหมดที่ส่งมาถึง To username นี้กลับออกไปในรูปของ JSON ทั้งหมดกลับออกไป

 

3. เมื่อได้ json กลับมา ตัว Knockout จะเอา json ไปสร้างรายการอีเมลใน inbox

อ่านย้อนกลับไปที่ข้อ 1.2, 1.3, 1.4, 1.5

 

4. เมื่อ user มี email รีเทิร์นค่ากลับมาก็ให้ทำการ connect to Push Service

จากนั้นใน javascript function InitialSSE(); จะเป็นการกำหนดตัว EventSource object ให้ชี้ไปยัง EmailServiceController (WebAPI) ซึ่งเป็น push service ที่เราสร้างขึ้นมาสำหรับส่ง email ใหม่ที่ส่งเข้ามาจากผู้ใช้อื่นๆ โดยจะมีการ add event ไว้ 3 ตัวแต่ใช้งานจริงแค่  ‘message’ event

   1: //EventSource Setup

   2: function InitialSSE() {

   3:     if (!!window.EventSource) {

   4:         var root = "@Url.Content("~/")";

   5:         source = new EventSource(root + 'api/EmailService/?email=' + viewModel.email());

   6:  

   7:         //Add message event

   8:         source.addEventListener('message', function (e) {

   9:             console.log(e.data); // log 

  10:             var mails = JSON.parse(e.data);

  11:             //alert from email

  12:             showNewMessageAlert(mails[0].From);

  13:             //binding array object to viewmodel(template binding)

  14:             viewModel.messages.valueWillMutate();

  15:             ko.utils.arrayPushAll(viewModel.messages(), mails);

  16:             viewModel.messages.valueHasMutated();

  17:             //sort inbox item

  18:             SortMessageDesc();

  19:  

  20:         }, false);

  21:  

  22:         source.addEventListener('open', function (e) {

  23:             console.log("open!");

  24:         }, false);

  25:  

  26:         source.addEventListener('error', function (e) {

  27:             if (e.readyState == EventSource.CLOSED) {

  28:                 console.log("error!");

  29:             }

  30:         }, false);

  31:     } else {

  32:         // not supported!

  33:         alert('The EventSource is not available.');

  34:     }

  35: }

  1. line3: ตรวจสอบว่า browser ที่ทำงานอยู่ปัจจุบันสามารถใช้งาน SSE ได้หรือไม่
  2. line4-5: เป็นการสร้างตัวแปร EventSource และกำหนด url ของ push service พร้อมทั้งส่งค่าของ email ของ user คนนี้ไปด้วย
  3. line8-20: มีการ add event ‘message’ ให้ตัว eventsource และเขียนคุมการทำงานให้ทำการแสดงผลเมื่อมี message ส่งมาจาก push service โดยทำงานกับ knockout และ jquery ในการแสดงผลข้อมูลที่ Inbox
  4. line22-24: บันทึกลง log เมื่อมีการเชื่อมต่อกับ push service แล้ว

เมื่อตัว EventSource ได้ถูกสร้างขึ้นก็จะยิง GET ไปยัง /api/EmailService/ ก็จะไปเข้า API Controller ของเราข้างล่าง

 

5. create long polling for online user (EmailServiceController)

การทำงานในขั้นต่อมาคือ มีการสั่งยิงมาด้วย GET (EventSource ทำงานโดยใช้ GET) มาเข้า HttpGet Action ซึ่งมีอยู่ตัวเดียวใน EmailServiceController ก็คือ

Get(HttpRequestMessage request)

   1: //EFcontext

   2: private MailBoxDBContext mbctx = new MailBoxDBContext();

   3: //message pool

   4: private static List<MailMessageView> _newMails = new List<MailMessageView>();

   5: //online subscribe user

   6: private static ConcurrentDictionary<string, SubscriberPushMail> _subscribers = 

   7:      new ConcurrentDictionary<string, SubscriberPushMail>();

   8:  

   9: //Get

  10: public HttpResponseMessage Get(HttpRequestMessage request)

  11: {

  12:      HttpResponseMessage response = request.CreateResponse();

  13:      var email = request.QueryString("email");

  14:  

  15:      if (!string.IsNullOrEmpty(email.Trim()))

  16:      {

  17:            response.Content = new PushStreamContent(

  18:                (stream, content, tc) =>

  19:                   {

  20:                        StreamWriter streamwriter = new StreamWriter(stream);

  21:                        if(_subscribers.ContainsKey(email))

  22:                        {

  23:                           SubscriberPushMail dead;

  24:                           if(_subscribers.TryRemove(email, out dead))

  25:                           {

  26:                                dead.Stream.Dispose();

  27:                                dead = null;

  28:                           }

  29:                        }

  30:                        _subscribers.TryAdd(email, new SubscriberPushMail

  31:                           {   Email = email, Stream = streamwriter });

  32:                    }, "text/event-stream");

  33:      }

  34:      return response;

  35: }

  1. line2: สร้างตัวแปรสำหรับ efcontext,
  2. line4: _newMails สำหรับเก็บอีเมลที่มีการส่งเข้ามาใหม่ และจะถูกลบออกเมื่อส่งออกไปแล้ว
  3. line6-7: _subscribers จะเป็นผู้ใช้ที่มีการ logon ล่าสุดเข้ามาเพื่อเก็บ connection ไว้ส่งข้อมูลกลับ
  4. Line10-35: จะเป็น Get Action สำหรับ EventSource ทำการเชื่อมต่อเข้ามา พร้อมรับ email ของ user เข้ามาเพื่อเพิ่มลงไปใน _subscribers เก็บทั้ง Email และ StreamWriter ของ HttpResponse เพื่อทำการส่งข้อมูลกลับไปในตอนหลัง และมีการตรวจสอบหากมี email นี้ใน _subscribers แล้วก็ให้ลบออกไปจากระบบ *แก้บั๊ก

 

6. Connected to Push Service

เมื่อรับ response กลับมาจาก Get() Action ด้านบนก็จะเกิด ‘open’ event ซึ่งให้ย้อนกลับไปดูข้อ 4.4 ด้านบน

 

7. เมื่อ User2 ทำการส่งอีเมลไปหา User1

ต่อไปเมื่อมี user2 เปิด browser เข้ามา load mailbox ของตัวเอง และทำการส่งอีเมลถึง user1 หลังจากกดปุ่ม Send

   1: $("#sendMail").click(function () {

   2:     $.ajax({

   3:         url: "/api/EmailService/",

   4:         data: JSON.stringify(ko.mapping.toJS(viewModel.newEmail)),

   5:         cache: false,

   6:         type: 'POST',

   7:         dataType: "json",

   8:         contentType: 'application/json; charset=utf-8',

   9:         success: function (data) {

  10:             var json = JSON.parse(data);

  11:             if (!json.success) {

  12:                 alert(data.msg);

  13:             }

  14:         }

  15:     });

  16:     viewModel.newEmail.To('');

  17:     viewModel.newEmail.Title('');

  18:     viewModel.newEmail.Message('');

  19: });

จะเข้ามาทำงานใน click ของ jquery ที่เราได้ hook event เอาไว้ โดยให้ทำการส่งไปที่ “/api/EmailService/”  และกำหนดข้อมูลโดยให้ส่ง viewModel.newEmail ไปด้วยในรูปแบบของ POST   และให้ทำการล้างค่าของ newEmail หลังจากทำงานเสร็จแล้ว

       Save and Push

หลังจากที่ jquery ได้ยิง POST เข้ามาที่ EmailService Controller ก็จะมาเข้าที่ SendEmail() Action เนื่องจากเป็น POST action ตัวเดียวใน controller นี้

   1: //Post

   2: public string SendEmail(MailMessageView newEmail)

   3: {

   4:     try

   5:     {

   6:         var user = mbctx.Users.SingleOrDefault(o => o.UserName == newEmail.Username);

   7:         if (user == null)

   8:             return Json.Encode(new {success = false, msg = "not found user"});

   9:         var dt = DateTime.Now;

  10:         newEmail.ReceivedDate = dt.ToString("dd/MM/yyyy HH:mm:ss");

  11:         var mail = new MailMessage

  12:                        {

  13:                            Id = newEmail.Id = Guid.NewGuid(),

  14:                            From = newEmail.From,

  15:                            To = newEmail.To,

  16:                            Title = newEmail.Title,

  17:                            Message = newEmail.Message,

  18:                            IsRead = false,

  19:                            ReceivedDate = dt,

  20:                            User_Id = user.Id

  21:                        };

  22:         mbctx.MailMessages.Add(mail);

  23:         mbctx.SaveChanges();

  24:         //add alert email if the target subscriber is online

  25:         if (_subscribers.ContainsKey(mail.To))

  26:         {

  27:             _newMails.Add(newEmail);

  28:             //push email to subscriber

  29:             PushMessageToClient(mail.To);

  30:         }

  31:         return Json.Encode(new {success = true});

  32:     }

  33:     catch (Exception ex)

  34:     {

  35:         return Json.Encode(new { success = false, msg = ex.Message });

  36:     }

  37: }

  38:  

  39: [NonAction]

  40: private void PushMessageToClient(string toEmail)

  41: {

  42:     var subscribers = _subscribers.Where(o => o.Key.ToLower() == toEmail.ToLower().Trim());

  43:     foreach (var subscriber in subscribers)

  44:     {

  45:         var isNewMail = false;

  46:         var mails = _newMails.Where(o => o.To.ToLower().Trim() == toEmail.Trim().ToLower()).ToList();

  47:         if (mails.Count > 0)

  48:         {

  49:             subscriber.Value.Stream.WriteLine("data:" + JsonConvert.SerializeObject(mails) + "\n");

  50:             try { subscriber.Value.Stream.Flush(); }catch { }

  51:             _newMails.RemoveAll(o => o.To == toEmail);

  52:         }

  53:     }

  54:  

  55:  

  56: }

  1. line6-23: ใช้ในการบันทึก new email ลง database
  2. line25-31: เป็นการตรวจสอบ user ที่จะ push และทำการ add email ลง message collection จากนั้นเรียก PushMessageToClient เพื่อส่ง email ไปให้ user คนนั้น
  3. line40: จะเป็น method ที่ใช้ในการ push
  4. line42-43: เลือก user ที่เราแอดเก็บไว้ในตอนแรกจาก email ของ user และทำการ loop ตามจำนวนที่พบ
  5. line46-47: ค้นหา email ที่ถูกส่งเข้ามาหา user นี้ออกมาทั้งหมด เมื่อนับแล้วมีมากกว่า 0 message
  6. line49-51: ใช้ stream ของ user นั้นและทำการสร้าง  data: โดยใช้เอา email มาทำ serialize ให้อยู่ในรูปของ json string แล้วสั่ง write / flush ตามลำดับ เมื่อจบก็ให้ remove email เหล่านั้นออกจาก collection

 

ทิ้งท้าย

ตัวอย่างสำหรับ Web Push ในตอนที่ 2 นี้จะมีแค่ส่วนของ SSE ซึ่งผมพยายามหยิบยกตัวอย่างที่ไม่ใช่การ broadcast message ซึ่งน่าจะพอเป็นไอเดียนำไปประยุกต์ใช้ต่อได้  ในตอนต่อไปจะเป็นการนำ websockets มาใช้งาน

โปรดติดตามตอนต่อไป Smile

 

Download Demo Project *มี database script อยู่ใน project folder เอาไปสร้าง database และอย่าลืมเปลี่ยน connectionstring ใหม่


SONY DSC

About Me:

Chalermpon Areepong : Nine (นาย)

Microsoft MVP Thailand ASP.NET

ASP.NET & MVC Developers Thailand Group :http://www.facebook.com/groups/MVCTHAIDEV/

Greatfriends.biz Community Leader : http://greatfriends.biz

Email : nine_biz-talk.net at hotmail dot com

Blog : https://nine69.wordpress.com

30/09/2012

ASP.NET MVC Series: Web Push Technology I

Filed under: ASP.NET, ASP.NET MVC, W3C, WEB — Tags: , , , — Nine MVP @ 8:30 pm

สำหรับบทความตอนนี้จะขอพูดถึงเทคโนโลยี่ของ HTML5 ตัวนึงที่ค่อนข้างมาแรงคือ Web Push Technology  ซึ่งเป็นเทคโนโลยีที่ช่วยให้เราลดปริมาณการส่งคำขอ request จาก Client browser ไปยัง server เพื่อให้ทำการประมวลผลตามคำสั่งตามคำขอนั้นอยู่ตลอดเวลา ตัวอย่างเช่น โปรแกรม chat ที่ต้องการดูคู่สนทนาพิมพ์โต้ตอบ หรือโปรแกรมstock ที่ต้องการดูความเปลี่ยนแปลงของค่าหุ้นเป็นต้น   

ซึ่งก่อนนี้เราจะต้องเขียนโปรแกรมให้ตั้งเวลาเพื่อสร้างคำขอไปยังserver ให้ทำการดึงข้อมูลที่เปลี่ยนแปลงมาอัพเดทยัง client browser ลักษณะการทำงานแบบนี้เราเรียกว่า Pull Model

Poll/Pull Technique มี 2 แบบคือ

1. Traditional Polling

คือการใช้ javascript : setInterval() และ setTimout() ในการทำงาน

setInterval(function(){
      $.ajax({ url: “/api/StockQuoteAPI”,
                   success: function(data){
                                   //Update your dashboard gauge
                                  stockQuoteGraph.setValue(data.value);
                                  },
                   dataType: “json”});
   }, 30000);
(function poll(){
        setTimeout(function(){
               $.ajax({ url: “/api/StockQuoteAPI”,
                            success: function(data){
                                           //Update your dashboard gauge 
                                           stockQuoteGraph.setValue(data.value);
                                           //Setup the next poll recursively
                                           poll();
                                          },
                            dataType: “json”});
          }, 30000);
})();

*ทั้ง setInterval(), setTimeout() ต่างก็จะทำงานเมื่อถึงเวลาที่กำหนดไว้ใน interval  สำหรับ setInterval() อาจจะทำงานซ้ำซ้อนกันหากความถี่ตั้งไว้น้อยและ server ตอบกลับช้า ข้อมูลที่ตอบกลับมาอาจจะไม่เรียงลำดับ

 

ปัญหาเรื่อง performance ของระบบ

image

ตามภาพด้านบนจะเห็นว่าทางซ้ายคือ stock exchange application ซึงทางฝั่งผู้ใช้ได้โหลดไปเพื่อดูข้อมูล แต่ทุก 10 วินาทีโปรแกรม JavaScript จะทำการสร้าง request และส่งคำสั่งไปยังเซอเวอร์เพื่อขอข้อมูลที่เปลี่ยนแปลง ซึ่งข้อมูลอาจจะไม่มีการเปลี่ยนแปลงเลยก็ได้ ซึ่งหากคำนึงถึงจุดนี้จะเห็นว่าอาจจะเป็นการเพิ่มการทำงานให้ตัว server ตามภาพด้านล่าง

 

image

ตามภาพด้านบนจะพบว่าหากมีผู้ใช้งานจำนวนเป็นหมื่นคน และต้องทำการรีเฟรชข้อมูลหุ้นอยู่ตลอดเวลาทั้งที่ข้อมูลอาจจะยังไม่มีการเปลียนแปลงใดๆ แต่ server ก็ต้องโปรเซสงานตามคำขอแม้ผลที่ได้คือไม่พบการเปลี่ยนแปลงของหุ้น ซึ่งอาจจะลงไปคิวรี่ในดาต้าเบสเพื่อหาความเปลี่ยนแปลงเป็นต้น

การทำงานที่ยิงตรงไป server เพื่อขอข้อมูลที่เปลี่ยนแปลงนั้นก็ยังนับว่าเป็นการทำงานที่ไม่ค่อยจะคุ้มค่าเท่าไหร่นัก หากทำงานแล้วพบว่ายังไม่มีข้อมูลเปลี่ยนแปลงและยังต้องทำงานวนเวียนอยู่แบบนั้น  ไม่เป็นการดีแน่หากจำนวนผู้ใช้งานเพิ่มมากขึ้นซึ่งแน่นอนว่าความสามารถในการรองรับจำนวนผู้ใช้งานนั้นมีอยู่จำกัด อาจจะทำให้เกิด timeout จนถึง server ล่มอยู่บ่อยๆ  ก็อาจจะต้องลงทุนอัพเกรดเครื่องหรือไม่ก็ซื้อเครื่องเพิ่มทำ load balance เพื่อช่วยให้รองรับการทำงานได้มากขึ้น

 

2. Long Polling

คือการใช้เทคนิคของ traditional polling เข้ามาประยุกต์โดยตั้งเวลาให้สร้างรีเควสแรกไปยังเซอเวอร์และหากยังไม่มีข้อมูลเปลี่ยนแปลงก็ให้ค้างรอจนกว่าจะมีข้อมูลถึงจะส่งกลับมายัง client และมีกำหนด timeout ของการ poll แต่ละครั้ง ซึ่งเป็นเทคนิคเดียวกับที่ comet มีใช้งานคล้ายกัน  วิธีการนี้เป็นการทำงานได้ผลคล้ายกับการใช้ push เพียงแต่ต้องไปสร้าง logic ที่ฝั่ง server เพื่อจัดการรีเควสนั้นจนจบเงื่อนไขต่างๆ

image

จาก Long Polling ทำให้เกิดแนวคิดมากมายทั้งถูกนำไปสร้างเป็น library อย่างเช่น COMET(รู้จักกันอีกชื่อว่า AJAX Push) มีคนนำไปพัฒนาเป็น Application ต่างๆมากมายไม่ว่าจะ meebo, gmail chat เป็นต้น ซึ่งช่วยให้ใช้งานง่ายขึ้น หลายเบราเซอร์ก็รองรับการทำงานเพราะใช้งาน javascript/xmlhttprequest ที่มีในเกือบทุกเบราเซอร์ กระทั่งมีแนวคิดพัฒนาต่อยอดเพื่อให้ทำงานเป็น Push จริงๆ จนกลายเป็นอีกความสามารถนึงที่มีมากับ HTML5 ในช่วงต่อไป

 

แล้วก็มาถึงยุค HTML5 แจ้งเกิด Web Push Technology

มีมาตรฐาน 2 ตัวจะเกี่ยวกับ Push Technology ที่มาพร้อมกับ HTML5 คือ

1. Server Sent Event(SSE)

เป็นคุณสมบัติที่ช่วยให้เราทำ server push message ได้ แต่ยังคงใช้เทคนิคของ long polling เป็นการทำงานพื้นฐาน และยังเพิ่มความสามารถในเรื่อง auto reconnect ได้เมื่อรีเควสได้ถูก closed ลงหรือเกิด timeout ขึ้น และสามารถกำหนด event ต่างๆ ส่งกลับมาบอกทาง client ได้ว่าเป็นข้อมูลที่เกิดจาก event อะไร

การตรวจสอบและเริ่มใช้งาน SSE สามารถใช้ script นี้ในการตรวจสอบได้

if (!!window.EventSource){
    var source = new EventSource(‘/api/StockQuoteAPI/’);
} else {
    // browser not support SSE, use xhr
}

รูปแบบข้อมูลของ SSE ที่ส่งรับกลับมาจะเป็น content type แบบ text/event-stream  จะเป็นลักษณะโครงสร้างดังนี้

1. event : เป็น field ที่เอาไว้บอกว่าเป็นเหตุการณ์ชื่ออะไรที่เกิดขึ้นที่ server  สามารถเพิ่มเข้าไปใน eventsource ได้ด้วยการสั่ง addEventListener() และหากไม่ได้กำหนดก็ดักด้วย onmessage handler ได้เลย

2. data : เป็น field ที่มีไว้ใช้ส่งข้อมูลจากฝั่ง server มายัง client สามารถส่งแบบ multiline ได้ รวมทั้งสามารถส่งข้อมูลแบบ json ได้ด้วย

กรณีส่งข้อมูลกลับแบบบรรทัดเดียว จะมี data: message และปิดท้ายด้วย \n\n ดังตัวอย่าง *ปกติเราจะมองไม่เห็น (\n) ในข้อความ

event: QuoteChange\n

data: Stock Quote Data Start!\n\n

กรณีที่มีการส่งข้อมูลแบบหลายบรรทัด แต่บรรทัดสุดท้ายจะมี \n\n ปิด ดังตัวอย่าง

event: QuoteChange\n

data: Stock Quote Data Start!\n

data: PTT 35.09\n

data: KTB 30.42\n\n

3. id : เป็นการจัดเรียงลำดับข้อมูลมาจาก server กรณีที่เกิด connection close ไปโดยสาเหตุใดๆก็ตาม ทำให้ client สามารถ sort data ได้

id: 1\n

event: QuoteChange\n

data: Hello world\n\n

4. retry : เป็นค่าเวลาในการสัง่รอให้เชื่อมต่อกลับไปยัง source link เมื่อ เกิด timeout ขึ้น ปกติค่าจะอยู่ที่ 3 seconds แต่เราก็สามารถแก้ไข interval สำหรับ reconnect ได้เอง

และหากต้องการเปิดการเชื่อมต่อกับฝั่ง server สามารถใช้คำสั่ง

source.close();

 

2. Web Sockets (ws)

ต้องร้องว้าวกันเลยสำหรับ feature นี้  เพราะว่าผมเองก็ไม่คิดว่าจะมีการนำ socket ที่เปิด tcp วิ่งไปมา มาใช้งานบนเว็บ   เนื่องจากอาจจะไม่สามารถทำงานผ่าน firewall ได้นั้นเอง  แต่ทว่าเขาสามารถทำให้ใช้งานผ่าน firewall ได้ด้วยอเมซิ่งเลยทีเดียว โดยถ้าคุณใช้งาน web ที่ port 80/443 ได้ปกติ คุณก็จะสามารถใช้งาน ws ได้ปกติเช่นกัน (ไว้อธิบายกันรอบหน้า)

ตัว ws นี้สามารถทำงานแบบ 2-way communications แบบซึ่งส่งข้อมูลได้ทั้งแบบ upstream/downstream ภายใน connection เดียวกัน   ซึ่งต่างกับแบบ comet, SSE ที่ต้องใช้ 2 connection ซึ่งและจะทำให้มีประสิทธิภาพในการทำงานที่ดีกว่ามาก

แต่ ws จะไม่สามารถทำงานแบบ P2P หรือ server remote to client ได้ เพราะสร้างไว้ส่งข้อมูล request เพียงอย่างเดียวกับการทำงานในแบบโมเดลแบบอื่นๆ

ส่วน protocol ที่รองรับนั้นจะมี plain text, soap, xmpp มีเท่านี้ ณ ตอนนี้

การใช้งานตัว ws

สามารถเริ่มจาก script นี้

if(“WebSocket” in window){ //if current browser is supported

   var myWs = new WebScoket(“ws://www.ninemvp.com/xws.svc”);

}

ตัว ws: จะเป็น protocol และตามหลังด้วย //endpoint address
แต่ถ้าทำงานบน SSL ก็กำหนดเป็น wss:

var myWs = new WebScoket(“wss://www.ninemvp.com/xws.svc”);

ตัว ws จะมี event ทั้งหมด 4 แบบคือ

  1. myWs.onopen เมื่อทำการเชื่อมต่อไปยัง ws server สำเร็จ
  2. myWs.onmessage เมื่อทาง server มีการส่ง message กลับมา
  3. myWs.onclose เมื่อทำการ close connection เสร็จแล้ว
  4. myWs.onerror เมื่อเกิด error ขึ้นในการทำงาน

ตัวอย่างการผูก event เพื่อดักการทำงาน

myWs.onopen = function() { alert(“Connection open …”); };

myWs.onmessage = function(msg) { alert( “Received Message: ” + msg.data); };

myWs.onclose = function() { alert(“Connection closed.”); };

myWs.onerror = function(msg) { alert( “Error Message: ” + msg.data); };

การส่งข้อมูลและรับข้อมูลกับทางฝั่ง server

การส่งข้อมูลใช้คำสั่ง

myWs.send(“test send data”);

myWs.close();

ส่วนการรับข้อมูลจะไปเข้า event onmessage ซึ่งเขียนดักการทำงานไว้ตามที่บอกไปข้างต้น

เราสามารถส่ง text ได้ทุกรูปแบบผ่านทาง ws ดังนั้นข้อมูลที่เป็น json ต่างๆก็ไม่ต้องกังวล

*ตัว ws จะใช้งานได้ก็ต่อเมื่อเรามี Web Socket Server มาใช้งานด้วยครับ ซึ่งก็มีหลากหลายให้ใช้งานเข้าไปดูกันได้ที่นี่ 

 

ทิ้งท้าย

ตอนนี้เป็นการแนะนำให้รู้จักว่าตั้งแต่อดีตจนถึงปัจจุุบัน เราสามารถส่งข้อมูลจาก server ไปยัง client ได้วิธีใดบ้าง ซึ่งมีหลายวิธีทั้ง push แท้และเทียมกันไป ในตอนหน้าเรามาเขียน mvc push application กันครับ

 


SONY DSC

About Me:

Chalermpon Areepong : Nine (นาย)

Microsoft MVP Thailand ASP.NET

ASP.NET & MVC Developers Thailand Group :http://www.facebook.com/groups/MVCTHAIDEV/

Greatfriends.biz Community Leader : http://greatfriends.biz

Email : nine_biz-talk.net at hotmail dot com

Blog : https://nine69.wordpress.com

19/09/2012

HTTP?

Filed under: ASP.NET, ASP.NET MVC, W3C, WEB — Nine MVP @ 12:01 am

HTTP คืออะไร

เราเขียนโปรแกรมทำงานกับมันแต่ไม่ได้สนใจรายละเอียดกันเท่าไหร่ อาจะเพราะ browser, script, framework ต่างๆ ช่วยซ่อนความซับซ้อนให้ แต่ยังไงคนทำเว็บก็ควรรู้ไว้เพราะมีประโยชน์ครับ

HTTP ย่อมาจาก Hypertext Transport Protocol เป็นโปรโตคอลสื่อสารที่ทำงานอยู่บนระบบโปรโตคอล TCP HTTP ใช้ในระบบเครือข่าย www (World Wide Web])

ใช้ในการแลกเปลี่ยนข้อมูลกันระหว่าง Server และ Client ที่เรียกใช้โดย  www   เราอ้างถึงทรัพยากร html, image, script, css, file, etc. ที่ใช้ผ่าน url  (Uniform Resoure Locators)  และแลกเปลี่ยนข้อมูลในรูปแบบภาษา HTML (HyperText Markup Language) โดยทำการแลกเปลี่ยนข้อมูลกันบน www

HTTP จะทำงานที่พอร์ต 80 เป็น defaultการส่งข้อมูลของ HTTP จะอยู่ในรูปแบบ Clear text คือ เป็นตัวอักษรต่างๆทุกรูปแบบ ไม่มีการเข้ารหัสใดๆส่วนมาตรฐานของ HTTP ปัจจุบันคือ HTTP 1.1 ซึ่งใช้กันอยู่ในปัจจุบันนี้

การทำงานของ HTTP คือโดยปกติจะเป็นการยิงไปรับกลับ Request-Response

การส่งข้อมูลไปกลับเราเรียกว่าเป็นช่วงของการทำ HTTP Session ระหว่าง client/server

โครงสร้างของ HTTP Request จะประกอบด้วย

  • request-line
  • general-headers
  • request-headers
  • entity-headers
  • empty-line
  • message-body
  • message-trailers

ตัวอย่าง ส่งคำขอไปยัง  http://localhost:12345/home/index 

GET: /home/index HTTP/1.1   ( reqest line

Accept: text/html,application/xhtml+xml; ( request-headers

Host: localhost:53596 ( request-headers

User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:16.0) Gecko/20100101 Firefox/16.0  ( request-headers

ใน Request Line จะมี HTTP Request Method นำหน้าเพื่อบ่งบอกว่าเป็น verb อะไร ใน HTTP/1.1  มีให้ใช้รวมทั้งหมด 9 ตัว

HEAD, GET,  POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH

แต่ส่วนใหญ่ใช้เขียนโปรแกรมบนเว็บแค่ GET, POST, PUT, DELETE ก็เพียงพอทำงาน ที่เหลือเอาไว้ใช้งานในระบบเซอวิสบริการอื่นๆ

ส่วน Accept จะบอกว่า ให้ web server ตอบกลับข้อมูลมาใน format ใด

Host ก็บอกว่าส่ง GET ไปที่ไหน

User-Agent บอกรายละเอียดของ client ว่า browser, plaform คืออะไรบ้าง

หลังจากที่ Web Server ประมวลผลแล้วก็จะตอบกลับมาเป็น HTTP Response มีโครงสร้างประมาณนี้

  • status-line
  • general-headers
  • response-headers
  • entity-headers
  • empty-line
  • message-body
  • message-trailers

ตัวอย่าง response ที่ได้จากการส่ง request ในข้างต้น

HTTP/1.1 200 OK ( status-line

Cache-Control: private ( response-headers

Content-Type: text/html; charset=utf-8 ( response-headers

Server: Microsoft-IIS/8.0 ( response-headers

X-AspNetMvc-Version: 4.0 ( response-headers

X-AspNet-Version: 4.0.30319 ( response-headers

X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcTmluZVxEb2N1bWVudHNcVmlzdWFsIFN0dWRpbyAyMDEyXFByb2plY3RzXE12Y0FwcGxpY2F0aW9uMVxNdmNBcHBsaWNhdGlvbjFcaG9tZVxpbmRleA==?=     ( response-headers

X-Powered-By: ASP.NET ( response-headers

Date: Tue, 18 Sep 2012 15:57:22 GMT ( general-headers

Content-Length: 395 ( response-headers

<!DOCTYPE html> ( message-body –> html document

<html>

<head>

<meta charset=”utf-8″ />

<meta name=”viewport” content=”width=device-width” />

<title>Index</title>

<link href=”/Content/site.css” rel=”stylesheet”/>

<script src=”/Scripts/modernizr-2.6.2.js”></script>

</head>

<body>

<h2>Index</h2>

<script src=”/Scripts/jquery-1.8.1.js”></script>

</body>

</html>

ตรง status-line จะบอกให้เรารู้ว่า web server ทำงานผลลัพธ์เป็นอย่างไรบ้าง ในตัวอย่างก็คือทำงานเป็นปกติ (STATUS 200) ซึ่งก็มีหลาย status number ให้เราเข้าใจว่าผลการทำงานผิดพลาดยังไง

ส่วนของ  response-header ที่เป็นรายละเอียดของ platform ค่อนข้างจะไม่ปลอดภัยเพราะมีแจ้งไว้หมดว่า

  • ใช้ web server อะไร
  • webapp พัฒนาด้วยอะไร
  • runtime version อะไร

หากเรารู้เราก็ควรจะปกปิดข้อมูลเหล่านี้ไว้ครับ ป้องกัน hacker เล่นงานในตอนหลัง

ยังมีข้อมูลอีกมากนะครับ อยากให้ทำความเข้าใจส่วนล่างสุดบ้าง ช่วยให้คุณทำงานได้ราบรื่นขึ้น รู้จริง รู้แจ้ง

ซึ่งช่วยแก้ไขปัญหาได้จริงๆซึ่งคุณไม่ควรมองข้าม เพราะกลุ่ม hacker มองหาช่องอยู่ตลอดเวลา 😉

ในอีกไม่นานนี้คาดว่าเราคงจะได้ใช้ HTTP 2.0 เรียกกันว่า HTTP Speed+Mobility ซึ่งจะถูกพัฒนาขึ้นโดย httpbis ซึ่งมีทั้ง google (SPDY) และ microsoft (HTTP S&M) ต่างช่วยทำ research ตั้งเป้าหมายที่จะทำ async connection multiplex, การบีบอัด header, มีท่อของ request-response, รองรับของ http1.1 ของเดิม  ขอบอกว่า Web socket คือคีย์หลักของงานนี้  เราก็ได้แต่ตั้งตารอคอยให้ถึงวันนั้นต่อไป

หวังว่าพอเป็นความรู้ได้บ้างครับ

Nine (นาย)

Microsoft MVP ASP.NET

Create a free website or blog at WordPress.com.