การใช้งาน HTTP Server และ HTTP Client ในภาษา Dart เบื้องต้น

30 December 2019 By Ninenik Narkdee
dart http server http client

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ dart http server http client



เนื้อหาในตอนต่อไปนี้ เราจะมารู้จักกับการใช้งาน
HTTP Server และ HTTP Client ในภาษา Dart กัน
และด้วยเนื้อหาเป็นเข้มข้น เป็นส่วนที่ต้องมีความเข้าใจเบื้องตันกับเนื้อหาก่อน
หน้ามาก่อน คือ จำเป็นต้องทำความเข้าใจ เกี่ยวกับการทำงานของโปรแกรมแบบ 
Asynchronous ด้วย Future และ Stream
    ทบทวนได้ที่
    การใช้งาน JSON String Data ในภาษา Dart เบื้องต้น http://niik.in/963
    ข้อมูล Stream การสร้าง และใช้งาน Stream ในภาษา Dart เบื้องต้น http://niik.in/962
    การใช้งาน Asynchronous Programming ในภาษา Dart เบื้องต้น http://niik.in/949
 
 
 

การส่งข้อมูลระหว่าง Serverr และ Client

    Server และ Client ใช้ HTTP protocal ติดต่อส่งข้อมูลระหว่างกันและกัน
ในภาษา Dart เราจะใช้ dart:io Library ในการจัดการเกี่ยวกับ HTTP Server จะคอยรับคำขอหรือ request 
บน Host และ Port ที่กำหนด และ Client ส่งคำขอหรือส่ง Rrequest โดยใช้คำสั่งใน HTTPClientRequest
    HTTP (Hypertext Transfer Protocol) เป็นช่องทางการติดต่อรับส่งข้อมูลจากโปรแกรมหนึ่ง ไปยังอีกโปรแกรมหนึ่ง
ผ่านเครือข่ายอินเตอร์เน็ต โดยฝั่งหนึ่งเป็น server และอีกฝั่งเป็น client ซึ่งโดยทั่วไปแล้วฝั่ง client จะหมายถึง
การใช้งานของ user ผ่านบราวเซอร์ หรือการทำงานของสคริปคำสั่งบนบราวเซอร์ นอกเหนือจากนั่น client ก็อาจจะเป็น
โปรแกรมใดๆ ที่ทำการรับส่งข้อมูลกับ server เป็นโปรแกรมแบบ standalone ทำงานเฉพาะ
 
    HOST และ PORT ถูกนำมาใช้ในการกำหนดการเชื่อมต่อของ server ผ่าน IP address และหมายเลข port  แล้วค่อยรับ
การ request จาก client   การทำงานในรูปแบบ Async (ขอใช้คำย่อนี้แทน Asynchromous) ทำให้ server จัดการกับ
request หลายๆ อันได้พร้อมกันในเวลาเดียว มีลำดับของการทำงานดังนี้
 
    - server คอยสังเกตและรับ request จาก client
    - client ทำการเชื่อมกับ server
    - server ตอบรับการเชื่อมต่อ และรับการ request (แล้วรอรับ request อื่นๆ ต่อ)
    - server สามารถตอบรับการ request อื่นๆ ได้
    - server ตอบกลับ request เดียวหรือ หลายๆ request ที่แทรกเข้ามา ด้วย response object
    - server สิ้นสุดการทำการ จบการ response หรือการตอบกลับ
 
ใน dart:io จะมี class และ ฟังก์ชั่นต่างๆ ที่จำเป็นในการใช้งาน HTTP ทั้งในฝั่ง server และ client  นอกจากนั้น ยังมี package
ที่ชื่อ http_server ที่เป็นการใช้งาน class ในระดับที่สูงกว่า ช่วยให้การจัดการการทำงานของ server ทำได้ง่ายขึ้น
 
 
 

การใช้งาน HttpServer

    HttpServer เป็นข้อมูลประเภท stream หนึ่งที่ปล่อย events ที่เป็น HttpRequest object ซึ่งทุก request จะมีความสัมพันธ์กับ
การใช้งาน HttpResponse object ตัวอย่างโค้ดด้านล่างเป็น Hello world server เหมือนการทำงานของ server ทั่วไป ที่ทำการส่ง
ข้อมูลหรือเนื้อหาต่างๆ เช่น หน้าเว็บเพจ ผ่านช่องทาง HTTP 
 
import 'dart:io';

void main() async {

  // กำหนด server ที่สามารถเชื่อมต่อผ่าน internet ทาง
  // IP address และ หมายเลข port ที่กำหนด
  // โดย server เป็นข้อมูล stream ประเภทหนึ่ง
  var server = await HttpServer.bind(
    InternetAddress.loopbackIPv4,
    4040,
  );

  // เมื่อกำหนด server เรียบร้อยแล้ว ตัว server ก็จะคอยสังเกต
  // คอยรับ request จาก client ถ้ามี ผ่าน host และ port ที่กำหนด
  print('Listening on localhost:${server.port}');

  // รอหากมีข้อมูล events ที่เป็น HttpRequest เกิดขึ้นใน server
  // กล่าวคือรอจนกว่า server จะได้รับการ request เข้ามา
  await for (HttpRequest request in server) {
    // ตอบกลับการ request ถ้ามีด้วย HttpResponse Object
    // โดยแสดงคำว่า "Hello, world!" 
    request.response.write('Hello, world!');
    await request.response.close(); // จบการทำงาน response object
  }

}
    ถ้าใครคุ้นเคยกับการใช้งาน NodeJs / ExpressJs ก็จะเป็นในลักษณะคล้ายๆ กัน เหมือนเราเขียนคำสั่งการทำงานที่ฝั่ง server เพื่อรอรับการ
เรียกใช้งานจาก client  
    เราใช้คำสั่ง HttpServer.bind() เพื่อกำหนด HttpServer object โดยกำหนด Hostname และ Port ที่ต้องการ ซึ่ง
hostname เป็นค่า parameter แรก สามารถกำหนดได้ทั้งชื่อ host เช่น localhost, example.com หรือ เป็น IP address เช่น 127.0.0.1
หรือจะใช้ค่ากำหนดเริ่มต้นที่มีมาให้ใน InternetAddress class เหมือนในตัวอย่างข้างต้น ที่ใช้เป็น "InternetAddress.loopbackIPv4"
 
 

    แนวทางการกำหนด Hostname

    - ใช้เป็น loopbackIPv4 หรือ loopbackIPv6 เมื่อต้องการให้ server คอยรับ request จาก client ในที่นี้เราจะใช้รูปแบบนี้แทนการกำหนดโดยใช้ localhost หรือ 127.0.0.1 
โดย IPv4 กับ IPv6 เป็นเวอร์ชั่นของ IP protocal (เวอร์ชั่น 4 กับ 6) เลือกใช้อย่างใดก็ได้
    ตัวอย่าง IPv4 Address: 192.168.1.1
    ตัวอย่าง IPv6 Address: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
    loopbackIP เป็น IP พิเศษที่ใช้สำหรับส่งสัญญาณออก กลับไปยังเครื่องที่เรียกใช้งาน ใช้สำหรับทดสอบระบบ
ที่เราคุ้นเคยกับการจำลอง server ที่เครื่องคอมพิวเตอร์โดยเรียกผ่าน localhost หรือ 127.0.0.1 เข้าใจอย่างง่ายก็คือ ให้เครื่องคอมพิวเตอร์
เครื่องเดียวเป็นได้ทั้ง server และ client ในเวลาเดียวกัน
 
    - ใช้เป็น anyIPv4 หรือ anyIPv6 เมื่อต้องการให้ server คอยรับ request จาก client ผ่าน IP addresss ใดๆ ก็ได้ทั้งที่เป็นเวอร์ชั่น 4
หรือเวอร์ชั่น 6 และ port ที่กำหนด
 
 

    แนวทางการกำหนด Port

    หมายเลข port เป็น parameter ส่วนที่สองของการเรียกใช้งานคำสั่ง bind() เป็นตัวเลขที่กำหนด port ซึ่งเป็นระบุการใช้งานเฉพาะ
อย่างใดอย่างหนึ่งที่ไม่ซ้ำกับ port อื่นของคอมพิวเตอร์ที่เป็น host  ตัวเลข port ที่น้อยกว่า 1024 จะถูกกันไว้ให้กับบริการหรือการใช้
งานมาตรฐาน (ยกเว้น 0)  ตัวอย่าง port ต่างๆ เช่น 20 สำหรับการใช้งาน FTP , HTTP ใช้เป็น 80 เป็นต้น
    ในการกำหด port ให้กับโปรแกรมของเราต้องกำหนดตั้งแต่ 1024 ขึ้นไป หากกำหนด port ที่มีการเรียกใช้งานจาก service อื่นแล้ว
การเชื่อมต่อไปยัง port นั้นๆ จะถูกปฏิเสธ และไม่สามารถใช้งานการรับส่งข้อมูลผ่าน port นั้นๆ ได้  ในตัวอย่างเราใช้เป็น 4040
 
 

    การรับ หรือดักจับการ Request

    เมื่อกำหนดช่องทางการเชื่อมต่อให้กับ server เรียบร้อยแล้ว server ก็จะเริ่มทำงานคอยสังเกต HTTP request  เหมือนกับว่าคอยยิง
คำถามว่า มี request เกิดขึ้นหรือยัง โดยใช้คำสั่ง "await for" เพื่อเรียกใช้งานข้อมูล stream และเมื่อ มี request เกิดขึ้น ก็จะทำการ 
เรียกใช้งาน HttpResponse object เพิ่อตอบกลับไปยัง client แล้วปิดการทำงานของ response object ของ request ที่เกิดขึ้น
    ในตัวอย่าง เมื่อมี request เกิดขึ้น ทุก request จะทำการส่งข้อความคำว่า "Hello, world!" ตอบกลับไป
 
  // รอหากมีข้อมูล events ที่เป็น HttpRequest เกิดขึ้นใน server
  // กล่าวคือรอจนกว่า server จะได้รับการ request เข้ามา
  await for (HttpRequest request in server) {
    // ตอบกลับการ request (ถ้ามี) ด้วย HttpResponse Object
    // โดยแสดงคำว่า "Hello, world!" 
    request.response.write('Hello, world!');
    await request.response.close(); // จบการทำงาน response object
  }
    ตอนนี้ถ้ารันคำสั่ง server จะแสดงข้ความว่า "Listening on localhost:4040" ในส่วน debug console และจะยังไม่มีข้อความจาก
การทำงานของ response object จนกว่าจะมี request จาก client เกิดขึ้น  ดังนั้น ให้เราทดสอบเปิดบราวเซอร์ แล้วพิมพ์ หรือเรียกไปยัง
http://localhost:4040/ ก็จะได้ผลลัพธ์ข้อความตอบกลับดังรูป
 
 

 
 
    ในกรณีนี้ server คือส่วนที่เราเขียนโปรแกรมภาษา Dart และ client คือส่วนการใช้งานบราวเซอร์  อย่างไรก็ตามเราสามารถเขียน
โปรแกรมภาษา Dart ในฝั่งการทำงานของ client ในลักษณะของสคริปคำสั่งผ่านบราวเซอร์ หรือจะเป็นลักษณะของโปรแกรมแบบ
standalone (โปรแกรมทั่วๆ ไปที่สร้างมาทำงานหรือคำสั่งเฉพาะ) ก็ได้
 
 
 

การใช้งาน HttpRequest

    เรามาทำความรุ้จักกับ HttpRequest เพิ่มเติม  
    HttpRequest object เป็นส่วนที่ถูกสร้างขึ้นมาโดย HttpServer เป็นเหมือนกับ events หนึ่ง ที่จะคอยรับข้อมูล request จาก client
โดยถ้า client มีการส่ง request เข้ามา HttpRequest object ก็จะถูกเพิ่มเข้าไปยังข้อมูล stream สังเกตการใช้งานในโค้ดที่ผ่านมา
 
  // เมื่อ HttpRequest ถูกเพิ่มเข้าไปใน server stream
  // เราก็สามารถเรียกใช้งานโดยใช้ await for เรียกใช้ข้อมูล request
  await for (HttpRequest request in server) {
    // ตอบกลับการ request ถ้ามีด้วย HttpResponse Object
    // โดยแสดงคำว่า "Hello, world!" 
    request.response.write('Hello, world!');
    await request.response.close(); // จบการทำงาน response object
  }
    เราสามารถใช้งาน HttpResponse object ซึ่งเป็นส่วนของการตอบกลับ โดยเรียกใช้งานจาก response property ของ HttpRequest
object นั่นคือ
 
var res = req.response;
// HttpResponse response = request.response;
    เราจะใช้ตัวแปรชื่อ res แทน response และ req แทน request เพื่อให้กระชับขึ้น
    HttpRequest object จะรับข้อมูลจาก client ในลักษณะของข้อมูล stream ในรูปแบบของ byte List<int> ซึ่งได้อธิบายไปบ้างแล้ว
ในตอนที่ผ่านมา http://niik.in/963 ตัวอย่างลักษณะของข้อมูลแบบ byte 
 
  var utf8HexBytes = [
    0x41, 0x42, 0x43
  ];
    ใน HttpRequest object จะประกอบไปด้วยข้อมูลต่างๆ ของการ request เช่น method (GET POST PUT DELETE อื่นๆ) 
URI หรือข้อมูลเกี่ยวกับ URL ต่างๆ  ข้อมูลเกี่ยวกับ headers  ข้อมูลเกี่ยวกับ cookie เหล่านี้เป็นต้น 
    ดูตัวอย่างการแสดงข้อมูล ของ HttpRequest object 
 
  await for (HttpRequest req in server) {
    print(req.uri);
    print(req.method);
    print(req.headers);
    req.response.write('Hello, world!');
    await req.response.close(); // จบการทำงาน response object
  }
    เมื่อเราเปิดบราวเซอร์ แล้วเรียกไปยังหน้า  http://localhost:4040/ ข้อมูลเกี่ยวกับ HttpRequest object ที่เราแสดงใน debug 
console โดยใช้คำสั่ง print จะเป็นดังตัวอย่างด้านล่าง
 
/
GET
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36 Edg/79.0.309.56
connection: keep-alive
cache-control: max-age=0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
accept-language: en-US,en;q=0.9,th;q=0.8
sec-fetch-mode: navigate
accept-encoding: gzip, deflate, br
sec-fetch-user: ?1
host: localhost:4040
sec-fetch-site: none
upgrade-insecure-requests: 1
 
    

    การใช้งาน RESTful GET request

    REST (REpresentational State Transfer) เป็นพื้นฐานการ request ข้อมูลผ่านเว็บไซต์ หรือบริการ web service โดย GET เป็น
รูปแบบการเรียกใช้งานหลัก เช่น เปิดไปหน้าเว็บไซต์ต่างๆ ดึงข้อมูลจาก Web API เบื้องต้น เหล่านี้ ล้วนมีการใช้งานในรูปแบบ 
GET Request เป็นหลัก ซึ่งเป็นลักษณะของ
    - รับและใช้งานข้อมูลเป็นหลัก
    - ไม่มีการเปลี่ยนแปลงค่าใดๆ บน server
    - มีข้อจำกัดในปริมาณความยาวของข้อมูล
    - สามารถส่งข้อมูลผ่าน query string ไปใน URL ของการ request ได้เช่น ?q=go&to=school
 
 

    การจัดการข้อมูลการ GET Request

    เราสามารถใช้ property ต่างๆ ของ HttpRequest object เป็นเงื่อนไขในการจัดการกับข้อมูล request ได้ เช่น การใช้งาน method 
property ยกตัวอย่าง สมมติเราเรียกยังหน้า http://localhost:4040/?q=go&to=school  แล้วให้ server แสดงข้อมูล query string 
กรณีมี method เป็น GET ดังนี้
 
  await for (HttpRequest req in server) {
    if (req.method == 'GET') {
      final action = req.uri.queryParameters['q'];
      final place = req.uri.queryParameters['to'];
      final formSubmit = '''
      <br>
      <form action="http://localhost:4040/" method="POST">
      <button type="submit">GO</button>
      </form>
      ''';
      await req.response.headers
        ..add(HttpHeaders.contentTypeHeader, 'text/html');
      await req.response
        ..write('${action} to ${place}')
        ..write('${formSubmit}')
        ..close();
    } else {
      await req.response
        ..statusCode = HttpStatus.methodNotAllowed
        ..write('Unsupported request: ${req.method}.')
        ..close();
    }    
  }
    เราใช้ req.method หรือ method property กำหนดเงื่อนไขการทำงานของข้อมูล request โดยถ้าเป็น GET ก็ให้ทำการเก็บข้อมูล
ของ query string จาก parameter สองค่าคือ "q" และ "to" ไว้ในตัวแปร action และ place ตามลำดับ โดยเรียกใช้งาน req.uri กำหนด
ในส่วนของ queryParameters จากนั้น ทำการเรียกใช้งาน HttpResponse object ผ่าน req.response โดยเราต้องการตอบกลับเป็นรูปแบบ
ของ html จึงกำหนดส่วนของ HttpHeaders โดยกำหนด contentType เป็น text/html 
 
      await req.response.headers
        .add(HttpHeaders.contentTypeHeader, 'text/html');
    ต่อไปก็ส่วนของข้อมูลการตอบกลับ ในที่นี้เราจะทำการเขียนข้อมูลกลับไป 2 ส่วน คือ ส่วนแรกเป็นการแสดงข้อมูลจาก query string
และส่วนที่สองเป็นการแสดงข้อมูลฟอร์ม ที่เราจำลองไว้สำหรับทดสอบ ส่งข้อมูลแบบไม่ใช่ GET เพื่อดูการทำงาน จะเห็นว่าเรามีการใช้งาน
(..) oprerator หรือตัวดำเนินการที่ทำให้เราสามารถเรียกใช้คำสั่งการทำงานแบบต่อเนื่องให้กับ object เดียวกันได้ 
นั่นคือให้ทำการตอบกลับ เป็นข้อมูลความตามค่า query string ที่ส่งมา ตามด้วยฟอรฺ์มข้อมูล แล้วทำการปิดการทำงานของ response 
โดยใช้คำสั่ง close()
    ซึ่งถ้าหากไม่ใช้เป็นการ request แบบ GET ก็ให้ทำการตอบกลับด้วยข้อความว่า "Unsupported request: POST." แทน
นอกจากนั้นยังทำการกำหนด statusCode ให้กับ response object เป็น 405 หรือ methodNotAllowed ดูสถานะที่เราสามารถ
กำหนดเพิ่มเติมได้ที่ HttpStatus 
 
    ผลลัพธ์ที่แสดงผ่านบราวเซอร์ และเปิดไปที่ http://localhost:4040/?q=go&to=school
 
 

 
 
    และเมื่อคลิกที่ปุ่ม GO
 
 

 
 
    จะเห็นว่าเราสามารถใช้ method ในการกำหนดการสร้าง RESTFul API ได้ โดยการกำหดเงื่อนไขการเรียกดูข้อมูลจาก method ต่างๆ
แล้วทำการส่งค่าตามเงื่อนไขของ method นั้น
 
 
 

การใช้งาน HttpResponse

    จากที่อธิบายไปแล้ว เรารู้ว่า HttpResponse object เป็น property หนึ่งของ HttpRequest object ที่ชื่อ response เป็นส่วนที่ใช้
ในการตอบกลับไปยัง client ซึ่งใน response object เราทำอะไรได้บ้าง มาดูเพิ่มเติมกัน
 
  await for (HttpRequest req in server) {
    var res = req.response; // return HttpResponse object
    final dataList = ['Hello','World'];
    final dataString = 'Hello World';
    final dataCharCode = 65; // เป็นตัวเลขฐาน 16 หรือฐาน 10
    // ตัวแปร res ใช้งาาน .. operator ตัวดำเนินการที่ทำงานต่อเนื่อง
    res
      ..writeAll(dataList,' | ') // เขียนจาก List แล้วมีตัวคั่น
      ..write(dataString) // เขียนแสดงข้อมูลทั่วไป
      ..writeCharCode(dataCharCode) // ตัวอักษณ A
      ..writeln(dataList); // เขียนข้อมูลพร้อมขึ้นบรรทัดใหม่ ไม่ใช่ <br>
     await res.close();
  }
    การ redirect ไปยัง url ที่ต้องการ ดูเนื้อหาเกี่ยวกับการจัดการ URL เพิ่มเติมที่ http://niik.in/951
 
  await for (HttpRequest req in server) {
    var res = req.response;
    var urlReditrect = 'https://www.ninenik.com';
    await res.redirect(Uri.parse(urlReditrect));
    await res.close();
  }
 
 

การใช้งาน HttpHeaders

    ใน HttpRespnse object มี property หนึ่งที่ชื่อ headers เป็นส่วนที่เราสามารถจัดการข้อมูลของ HttpHeaders object ที่ใช้สำหรับ
ตอบกลับไปยัง client ไม่ว่าจะเป็นกำหนดประเภทข้อมูลการแสดงโดยกำหนด contentType หรือกำหนดสถานะ statucCode หรือข้อมูล
อื่นๆ เพิ่มเติม
 
  await for (HttpRequest req in server) {
    var res = req.response;
    var headers = res.headers; // return HttpHeaders object
    var jsonData = '''
    {
      "name": "Ebiwayo",
      "address": {
          "street": "My St.",
          "city": "New York"
      }
    }
    ''';
/*     headers.contentType 
      = ContentType('application', 'json', charset: 'utf-8'); */
    headers
      ..add(HttpHeaders.contentTypeHeader,ContentType.json.toString());
    await res.write(jsonData);
    await res.close();
  }
    เราสามารถใช้ค่าคงที่ของ ContentType ต่างเช่น ContentType.text ContentType.html ContentType.json หรือ
ContentType.binary เป็นต้น 
    นอกจากนั้นเรายังสามารถกำหนดส่วนของ headers ที่ต้องด้องการในรูปแบบดังนี้ได้ เช่น
 
    headers
      ..add('Content-Type','application/json; charset=utf-8')
      ..add('Access-Control-Allow-Origin','*');
    ดูลิสรายการของ headers property เพิ่มเติมได้ที่ List of HTTP header fields    
    เพิ่มเติม ContentType 
 
 
 

การใช้งาน HttpStatus

    เราสามารถกำหนด statucCode ให้กับ ข้อมูลที่ตอบกลับไปยัง client ตามเงื่อนไขที่ต้องการได้ ซึ่งโดยค่าเริ่มต้นจะเป็น 200 หรือ
กรณีใช้ HttpStatus object ก็จะเป็น HttpStatus.ok เรามาลองใส่ ststusCode เป็น 404 หรือ ไม่พบ URL ดังกล่าว เมื่อมีการ
เรียกเข้ามาเป็นดังนี้
 
  await for (HttpRequest req in server) {
    var res = req.response;
    var headers = res.headers; // return HttpHeaders object
    var statusDataOk = '''
    {
      "message": "successful",
      "code": 200
    }
    ''';
    var statusDataFail = '''
    {
      "message": "fail",
      "code": 404
    }
    ''';    
    headers.add(
      HttpHeaders.contentTypeHeader,ContentType.json.toString());    
    if(req.method == 'GET'){
      await res
          ..statusCode = HttpStatus.notFound
          ..write(statusDataFail);
    }else{
      await res
          ..statusCode = HttpStatus.ok
          ..write(statusDataOk);
    }
    await res.close();
  }
    เราใช้วิธีการตอบกลับสถานะของข้อมูลในรูปแบบ JSON โดยสร้างเงื่อนไขทดสอบเมื่อ มี method GET เข้ามา เราเพิ่มการกำหนด
statusCode เป็น HttpStatus.notFound หรือ 404 และเขียนข้อมูล JSON ตามเงื่อนไขกลับไปยัง client เป็นลักษณะของการใช้งาน
RESTful API  (* ในรูปที่แรก ต้องเป็น 404 ค่าผิด)



 
 
 
 
    ดูเพิ่มเติมเกี่ยวกับ HttpStatus 
 
    ตอนนี้เราได้รู้จัก และทำความเข้าใจ การใช้งานในฝั่ง server ไปพอสมควรแล้ว ต่อไป เราจะไปดูในส่วนของการกำหนด และใช้งาน
ในฝั่งของ client และมักจะถูกเรียกใช้งานบ่อย เมื่อต้องทำการดึงข้อมูล หรือเรียกใช้ข้อมูลจาก server มาใช้งาน รวมถึงการใช้งาน
POST request ฝั่ง server เพิ่มเติม กรณี client ส่งส่งข้อมูลเป็นแบบ POST request
 
 
 

การใช้งาน HttpClient

    HttpClient object เป็นส่วนของการใช้งานฝั่ง client ที่มีคำสั่งต่างๆ ที่ใช้สำหรับส่งข้อมูล HttpClientRequest ไปยัง http server
และรับข้อมูล HttpClientResponse กลับมาใช้งาน ตัวอย่างคำสัง เช่น get() getUrl() post() และ postUrl() สำหรับการส่งข้อมูล
แบบ GET และ POST ตามลำดับ
    ในที่นี้เราจะพูดถึง client ที่เป็นโปรแกรม standalone เช่น App หรือโปรแกรมที่มีการใช้งานเชื่อมต่อ รับส่งข้อมูลกับ server ที่ไม่ใช้
รูปแบบการใช้งานผ่านการเรียก url ในบราวเซอร์ เหมือนที่เรียกใช้ในตัวอย่างที่ผ่านมาด้านบน
 
 

    การใช้งาน GET Request

    เราจะใช้ข้อมูลจากฝั่ง server เป็นข้อมูลตัวอย่างจากเว็บไซต์ https://jsonplaceholder.typicode.com/ โดยเรียก API ในส่วนของ
users โดยใช้ url เป็น https://jsonplaceholder.typicode.com/users  ในที่นี้เราจะสร้าง model ข้อมูล users ไว้ในไฟล์เดียวกับ
ไฟล์ main.dart เพื่อทดสอบการทำงานเท่านั้น ดูการสร้าง model ข้อมูลได้ที่บาทความตอนที่แล้วเกี่ยวกับการใช้งาน JSON
    การใช้งาน JSON String Data ในภาษา Dart เบื้องต้น http://niik.in/963
 
    โค้ดเต็มการใช้งาน GET Request ดึงข้อมูลจาก API
 
import 'dart:convert';
import 'dart:io';

void main() async {

  // กำหนด url ของ API ที่ต้องการเรียกดูข้อมูล
  var url = 'https://jsonplaceholder.typicode.com/users';
  var clientRequest = await HttpClient().getUrl(Uri.parse(url));
  var clientResponse = await clientRequest.close();

  // แปลงข้อมูล Stream ถอดรหัส utf8 และแปลง JSON String data
  var jsonData = utf8.decoder.bind(clientResponse)
                .transform(JsonDecoder());

  try {
    // ใช้งานข้อมูล Stream
    await for (var data in jsonData) {
      print(data.runtimeType); // List<dynamic>
      var userList = ListUser.fromJson(data); // สร้าง ListUser object
      print(userList.runtimeType); // ListUser
      for(var user in userList.users){ // วนลูปแสดงชื่อ user
        print('User ID: ${user.id} Nmae: ${user.name}');
      }
    }
  } catch (e) {
    print(e);
  }  
  

}

class ListUser{
  List<User> users;
  ListUser({this.users});

  factory ListUser.fromJson(List<dynamic> json) {
    return ListUser(
        users: json
            .map((e) => User.fromJson(e as Map<String, dynamic>))
            .toList());
  }

}

class User {
  int id;
  String name;
  String username;
  String email;
  Address address;
  String phone;
  String website;
  Company company;

  User(
      {this.id,
      this.name,
      this.username,
      this.email,
      this.address,
      this.phone,
      this.website,
      this.company});

  User.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    name = json['name'];
    username = json['username'];
    email = json['email'];
    address =
        json['address'] != null ? new Address.fromJson(json['address']) : null;
    phone = json['phone'];
    website = json['website'];
    company =
        json['company'] != null ? new Company.fromJson(json['company']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['name'] = this.name;
    data['username'] = this.username;
    data['email'] = this.email;
    if (this.address != null) {
      data['address'] = this.address.toJson();
    }
    data['phone'] = this.phone;
    data['website'] = this.website;
    if (this.company != null) {
      data['company'] = this.company.toJson();
    }
    return data;
  }
}

class Address {
  String street;
  String suite;
  String city;
  String zipcode;
  Geo geo;

  Address({this.street, this.suite, this.city, this.zipcode, this.geo});

  Address.fromJson(Map<String, dynamic> json) {
    street = json['street'];
    suite = json['suite'];
    city = json['city'];
    zipcode = json['zipcode'];
    geo = json['geo'] != null ? new Geo.fromJson(json['geo']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['street'] = this.street;
    data['suite'] = this.suite;
    data['city'] = this.city;
    data['zipcode'] = this.zipcode;
    if (this.geo != null) {
      data['geo'] = this.geo.toJson();
    }
    return data;
  }
}

class Geo {
  String lat;
  String lng;

  Geo({this.lat, this.lng});

  Geo.fromJson(Map<String, dynamic> json) {
    lat = json['lat'];
    lng = json['lng'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['lat'] = this.lat;
    data['lng'] = this.lng;
    return data;
  }
}

class Company {
  String name;
  String catchPhrase;
  String bs;

  Company({this.name, this.catchPhrase, this.bs});

  Company.fromJson(Map<String, dynamic> json) {
    name = json['name'];
    catchPhrase = json['catchPhrase'];
    bs = json['bs'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['name'] = this.name;
    data['catchPhrase'] = this.catchPhrase;
    data['bs'] = this.bs;
    return data;
  }
}
 
    ผลลัพธ์ที่ได้เมื่อเรียกใช้งาน 
 
 

 
 
    

    การใช้งาน HttpClientRequest และ HttpClientResponse 

    ขออฺธิบายบางส่วนในลำดับการทำงานดังนี้
 
  // กำหนด url ของ API ที่ต้องการเรียกดูข้อมูล
  var url = 'https://jsonplaceholder.typicode.com/users';
  // client เริ่มเชื่อมต่อกับ Network ไปยัง server ผ่าน Url ที่กำหนด
  // เมื่อใช้คำส่ัง getUrl() และเมื่อเชื่อมสำเร็จจะคืนค่า เป็น HttpClientRequest object
  // เราสามารถกำหนดค่าให้กับ HttpClientRequest เพิ่มเติมได้ถ้ามี เช่น contentType
  // หรือข้อมูลอื่นๆ ที่เป็น HttpHeaders ด้วยคำสั่ง set() หรือ add() เช่นเดียวกับ HttpRequest ฝั่ง server
  var clientRequest = await HttpClient().getUrl(Uri.parse(url));
  // เมื่อใช้คำส่ัง close() client ส่งคำขอ หรือ request ไปยัง server 
  // server คืนค่ากลับเป็น HttpClientResponse object ซึ่งมีข้อมูลต่างๆ
  var clientResponse = await clientRequest.close();

  // แปลงข้อมูล Stream จาก HttpClientResponse object ให้พร้อมใช้งาน
  // โดยถอดรหัส utf8 และแปลง JSON String data
  var jsonData = utf8.decoder.bind(clientResponse)
                .transform(JsonDecoder());
    ตัวอย่างเพิ่มเติม การแสดงข้อมูลไฟล์ text จากเว็บไซต์ www.w3.org โดยใช้คำสั่ง get()
 
import 'dart:convert';
import 'dart:io';

void main() async {

  var host = 'www.w3.org';
  var textFile = '/TR/PNG/iso_8859-1.txt';
  var clientRequest = await HttpClient().get(host, 80, textFile);
  var clientResponse = await clientRequest.close();
  var lines = utf8.decoder.bind(clientResponse)
                .transform(LineSplitter());

  try {
    await for (var line in lines) {
      print(line); 
    }
  } catch (e) {
    print(e);
  }  
  
}
 
 

    การใช้งาน RESTful POST request

    จะคล้ายๆ กับ GET request แต่เป็นได้ทั้งลักษณะ การเรียกดูข้อมูล หรือการส่งข้อมูลไปใช้งาน 
    - เตรียมข้อมูลที่จะส่งไปยัง server
    - กำหนด URL ที่มีโครงสร้างเหมือนไฟล์และโฟลเดอร์ เช่น https://www.example.com/users/post ไม่มี query string
    - ส่งข้อมูลในรูปแบบ JSON หรือ XML
    - ไม่มีการเปลี่ยนแปลงค่าใดๆ บน server
    - ไม่จำกัดปริมาณความยาวของข้อมูล เหมือนกรณี GET request
 
 

    การใช้งาน POST Request

    เราจะลองทำการ ส่งข้อมูลแบบ POST ไปยัง ข้อมูลจำลองของเว็บไซต์ https://jsonplaceholder.typicode.com/ โดยจะเพิ่ม users
ลำดับที่ 11 เข้าไป เป็นข้อมูลอย่างง่าย ซึ่งขั้นตอนการส่ง เราต้องเตรียมข้อมูลที่จะส่งในรูปแบบ Map<string, dynamic> 
กำหนด url ของ API ที่เราจะส่งข้อมูลไปเพิ่ม  ดูโค้ตตัวอย่าง และคำอธิบายเพิ่มเติมในโค้ด
 
import 'dart:convert';
import 'dart:io';

void main() async {

  // เตรียมข้อมูลที่จะส่งไปยัง server ในรูปแบบ Map<String, dynamic>
  var jsonData = {
    'name': 'Ebiwayo',
    'username': 'ebiwayo',
    'email': 'demo@example.com'
  };
  // กำหนด URL ของ API 
  var url = 'https://jsonplaceholder.typicode.com/users';

  // ใช้คำส่ัง posUrl() โดยเพิ่ม headers ให้กับ Header และ data ให้กับ
  // HttpClientRequest object
  var clientRequest = await HttpClient().postUrl(Uri.parse(url))
          ..headers.contentType = ContentType.json // เพิ่ม header
          ..write(jsonEncode(jsonData)); // เพิ่ม data ่แปลงเป็น json string
  var clientResponse = await clientRequest.close();
  // แปลงข้อมูลที่ได้กลับมา จะเป็นข้อมูล ที่เราเพิ่งเพิ่มเข้าไป
  var dataAdded = utf8.decoder.bind(clientResponse)
                .transform(JsonDecoder());

  try {
    await for (var data in dataAdded) {
      print(data.runtimeType); 
      print(data); 
    }
  } catch (e) {
    print(e);
  }  
  
}
    จะเห็นว่าในการส่งข้อมูล จะส่งข้อมูลในรูปแบบ JSON โดยมีการเพิ่ม headers และข้อมูลที่ส่งหรือ data เข้าไปใน HttpClientRequest
object และเมื่อเรียกใช้คำสั่ง close() ก็จะเป็นการส่งข้อมูลไปยัง server แล้วรับค่ากลับมาเป็น HttpClientResponse object เป็นข้อมูล 
stream ทำการแปลงข้อมูลสองขั้นคือถอดรหัส utf8 และแปลงข้อมูลจาก JSON String data จากนั้นก็นำข้อมูลไปใช้งานต่อ ในที่นี้เรา
แค่แสดงชนิดของข้อมูล และค่าของข้อมูล โดยใช้คำสั่ง print() เท่านั้น ผลลัพธฺ์ที่ได้ตามรูปด้านล่าง
 
 

 
 
 

    การจัดการ POST Request ฝั่ง Server

    เนื้อหาเกี่ยวกับ GET Request ฝั่ง server เราได้อธิบายไปแล้วในหัวข้อ การใช้งาน HttpServer และตอนนี้เราได้รู้ว่า Client มีการส่ง
ข้อมูล POST Request ในรูปแบบและลักษณะอย่างไรไปแล้วในหัวข้อที่ผ่านมา หัวข้อนี้เราจะมามองในฝั่ง server ที่ client ส่งข้อมูล
มาดังกล่าวข้างต้นแล้ว เราจะจัดการข้อมูลได้อย่างไร ดูตัวอย่างโค้ดด้านล่างประกอบ
 
import 'dart:convert';
import 'dart:io';

void main() async {

  var server = await HttpServer.bind(
    InternetAddress.loopbackIPv4,
    4040,
  );

  print('Listening on localhost:${server.port}');

  await for (HttpRequest req in server) {
    var contentType = req.headers.contentType; // เก็บค่า contentType
    var res = req.response; // HttpResponse obect
    var headers = res.headers; // HttpHeaders Object    

    // กำหนด header รูปแบบการตอบกลับเป็นข้อมูล jSON
    headers
      ..add('Content-Type','application/json; charset=utf-8')
      ..add('Access-Control-Allow-Origin','*');

    // ตรวจสอบเงื่อนไข ถ้าเป็นการส่งข้อมูลแบบ POST และเป็นแบบ json
    if (req.method == 'POST' &&
        contentType?.mimeType == 'application/json' ) {

      try {
        // ถอดรหัส utf8 และ รวมข้อมูล json เป็น String ข้อความเดียว
        var content = await utf8.decoder.bind(req).join();
        var data = jsonDecode(content) as Map; // แปลง JSON เป็น Dart object
        print(content);
        res
          ..write(jsonEncode(data));
      } catch (e) {
        var simpleError = {'msg': '${e}'};
        res
          ..write(jsonEncode(simpleError));
      }
    } else {
      var simple = {'msg': 'Hello World'};
      res
        ..write(jsonEncode(simple));
    }
    await res.close(); // จบการทำงาน response object
  }

}
    เรากำหนดการทำงานของฝั่ง server ด้วยภาษา dart โดยมีการตรวจสอบ request ที่ส่งมาจาก client ว่าเป็น contentType หรือ
ชนิดข้อมูลแบบ json หรือไม่ และเป็นการส่งข้อมูลแบบ POST หรือไม่ และกำหนดการตอบกลับเป็นรูปแบบข้อมูล json โดยมีการใช้งาน
การกำหนด headers ให้กับ HttpResponse หากข้อมูลที่ส่งเข้ามาตรงเงื่อนไข ก็ให้ทำการ ถอดรหัส request ทุก request แล้วรวมกัน
เป็นข้อความ JSON String ด้วยคำสั่ง join() ไว้ในตัวแปร content และทำการแปลงเป็น Dart object หรือข้อมูลชนิด Map ไว้ในตัวแปร
data ในโค้ด เราทำการตอบกลับข้อมูลในรูปแบบ JSON ในการใช้คำสั่ง write() จึงทำการเข้ารหัส JSON ให้กับตัวแปร data
เพื่อแสดงกลับไปยัง client 
    ตอนนี้เราเตรียมในฝั่ง server เรียบร้อยแล้ว แต่เราจะไม่เรียกใช้งานฝั่ง client ด้วยภาษา Dart เหมือนตัวอย่างก่อนหน้าด้านบน แต่จะ
จำลองเรียกใช้งานผ่าน console ของ บราวเซอร์ โดยใช้งาน Fetch API
    
    จำลองการส่งค่าแบบ POST ในรูปแบบข้อมูล json ตามตัวอย่างคำสั่งด้านล่าง
 
var data = { username: 'example' };

fetch('http://localhost:4040/', {
  method: 'POST', 
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})
.then((response) => response.json())
.then((data) => {
  console.log(JSON.stringify(data));
})
.catch((error) => {
  console.error(JSON.stringify(error));
});
    นำคำสั่งไปรันในส่วนของ console ของบราวเซอร์ตามรูป แล้วดูผลลัพธ์
 
 

 
 
    เมื่อเราทำการส่งข้อมูลแบบ POST ไปยัง server ตามโค้ดก็จะได้ข้อมูล ที่เป็น data ที่เราส่งไป ถูกส่งกลับมาจาก server ตามที่เรา
กำหนดโค้ดการทำงานไว้ เนื้อหาหัวข้อนี้ จึงเป็นแนวทางสำหรับการจัดการในฝั่ง server กรณีที่ client ส่งข้อมูลแบบ POST เข้ามา
 
    เราได้รู้จักกับการใช้งานเกี่ยวกับ HTTP ในภาษา dart ไปพอสมควร ยังมี method และการใช้งานอื่นๆ ที่สามารถประยุกต์ได้ เช่น delete() 
put() เป็นต้น ดูคำสั่ง อื่นๆเพิ่มเติมของ HttpClient ได้ที่ HttpClient Class
 
    นอกจากการใช้งาน dart:io ที่ใช้ในการจัดการ HTTP Server และ Client ตามเนื้อหาข้างต้นที่ได้แนะนำไปแล้ว ยังมี http_server 
package อีกตัว ที่ช่วยให้เราจัดการการทำงานในส่วนของ HTTP Server ได้สะดวกและง่ายขึ้น แต่ก็ถือเป็นเนื้อหาเพิ่มเติม อาจจะได้นำมา
ทำความเข้าใจ หรืออาจะเพิ่ม เป็นเนื้อหาเพิ่มเติม ต่อไป


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ







เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ