วันอาทิตย์ที่ 26 เมษายน พ.ศ. 2563

วิธีเปิดโหมดนักพัฒนา ( Developer Option )บนมือถือ andriod


วิธีเปิดโหมดนักพัฒนา ( Developer Option )บนมือถือ andriod

วิธีง่าย ๆ ตามขั้นตอนนี้
1. ไปที่ Setting 
2. เลือก About เมนูล่างสุด

3. ไปยัง Software information

4. กดคลิกไปที่ Build Number ประมาณ 3 รอบติดกัน ก็จะมีเมนู Developer Options ขึ้นมาค่ะ เท่านี้ก็สามารถใช้โหมดนักพัฒนาได้แล้ว
Stay awake
เป็นโหมดให้ที่เวลาเราชาร์ตไฟแล้วไฟหน้าจอจะไม่ดับ
USB debugging
เมื่อเราติ๊กถูกตรงนนี้จะเป็นการเชื่อมต่อโดยตรง และจะไม่เรียกใช้งานข้อมูลตัวเครื่องโทรศัพท์ผ่านทางโปรแกรมเฉพาะบน PC ส่วนใหญ่ใช้เวลาแฟลชรอมครับซึ่งถ้าไม่ได้ทำอะไรแบบนั้นก็ไม่ต้องเลือกก็ได้
Allow mock locations
ใช้ทดสอบ location สำหรับการ develop application ผ่าน debugging
Show touches
อันนี้ก็เป็นการแสดงตำแหน่งการสัมผัสบนหน้าจอแต่จะไม่ระเอียดเหมือน Pointer location
Pointer location
เป็นการแสดงตำแหน่งของการสัมพัสของหน้าจ มีบอกตัวเลขตำแหน่งเส้นที่เราลากนิ้วไป
Show Surface updates
เป็นการแสดงแสง Flash บนหน้าจอของเราเวลาที่มีการอัพเด (เวลาเรากดหน้าจอจัมีแสงแว๊บๆบนจอดูเหมือนเครื่องพังเลยทีเดียวเชียว)
Show Layout bounddaries
สำหรับดู layout ของวัตถุต่างๆในแอพ (เหมาะสำหรับคนที่ดีไซน์หน้าตาแอพ)
Show CPU usage
แสดงว่าตอนนี้ CPU กำลังทำงานอะไรอยู่
Force GPU rendering
ใช้ในการเพิ่มประสิทธิภาพฮาร์ดแวร์ 2d ในแอพพลิเคชั่น ไปหาข้อมูลมาเค้าบอกว่าเวลาเจอเกมส์หรือแอพอะไรที่เป็น 2D อะไรกระตุกๆก็ลองเลือกดูจ้า
Don’t keep activities
เคยเจอไหมเวลาเราเล่นเกมอะไรอยู่แล้วต้องการจะเลิกเล่นก็กดปุ่ม Home แต่พอกลับเข้าไปในเกมมันก็ยังทำงานอยู่ตรงนี้แหละถ้าเลือกไม่ว่าเราจะออกจะแอพอะไรก็ตามไม่ว่าด้วยวิธีไหนมันจะปิดแอพให้โดยสมบูรณ์
Background process limit
จำกัดการทำงานของแอพเบื้อหลัง แอพเบื้องหลังคืออะไรแอพที่ทำงานอยู่แล้วเราเรียกแอพใหม่ขึ้นมาใช้และสลับไปมาเพื่อดูเข้าออกจากโปรแกรมนั้นไปโดยไม่ได้ปิด เช่น เล่นเกมอยู่แล้วสลับไปดูเว็บ

วันจันทร์ที่ 20 เมษายน พ.ศ. 2563

ทำระบบ Realtime บน Web

ทำระบบ Realtime บน Web

ถ้าต้องทำระบบ chat, dashboard, กราฟ ที่ต้องการให้ข้อมูล update เอง จะทำยังไงดี ?
ระบบ Realtime ที่ส่วนใหญ่นิยมใช้ใน web มี
  1. Short Polling
  2. Long Polling
  3. Websocket
  4. Server-Sent Event (SSE)

Short Polling

คือการที่เราตั้งเวลาให้ไปดึงข้อมูลทุก ๆ n วินาที

ข้อดีของ Short Polling

  • เป็นวิธีทำระบบ realtime ที่ง่ายที่สุด
  • รันที่ไหนก็ได้ เพราะเป็นการเรียก HTTP ธรรมดา

ข้อเสียของ Short Polling

  • เปลือง resource มาก เพราะถึงข้อมูลไม่เปลี่ยน ก็จะมีการส่งข้อมูลตลอดเวลา

ตัวอย่างการทำ Short Polling

ฝั่ง Server ไม่ต้องทำอะไรเลย (ง่ายไหม 🙈)
func getDataShortPolling(w http.ResponseWriter, r *http.Request) {
    data := getData()

    w.Write(data)
}
ฝั่ง Browser ทำได้ง่าย ๆ 2 วิธี
  1. setInterval - เขียนง่าย แต่ถ้า server ช้าจะโดนยิง
     setInterval(fetchData, 3000) // ดึงข้อมูลทุก 3 วินาที
    
  2. setTimeout - ยังเขียนง่ายอยู่
     async function startPolling () {
         await fetchData() // รอให้ดึงข้อมูลเสร็จก่อน ค่อยรออีก 3 วินาที
         setTimeout(startPolling, 3000)
     }
    
     startPolling()
    

Long Polling

คล้าย ๆ Short Polling แบบที่ 2 แต่ไม่ต้องมี setTimeout และให้ Server รอจนกว่าข้อมูลเปลี่ยนค่อยตอบ api กลับมา

ข้อดีของ Long Polling

  • ประหยัด resource กว่า Short Polling
  • รันที่ไหนก็ได้ เพราะเป็นการเรียก HTTP ธรรมดา

ข้อเสียของ Long Polling

  • มีโอกาสโดน timeout บ่อย
  • อาจจะมีถ้าเขียนแบบง่าย ๆ อาจจะมีช่วงที่ไม่ได้ข้อมูลล่าสุด (api ตอบ response กลับไป แล้วมี data change ก่อนที่ client จะยิง api เข้ามาอีกรอบ)

ตัวอย่างการทำ Long Polling

ฝั่ง Server ต้องรอให้ Data เปลี่ยนก่อน ค่อยตอบกลับไป
func getDataLongPolling(w http.ResponseWriter, r *http.Request) {
    if r.URL.Query().Get("long") != "1" {
        getDataShortPolling(w, r)
        return
    }

    select {
    case <-untilNewDataReceived():
    case <-r.Context().Done():
        // client close connection
        return
    }


    data := getData()

    w.Write(data)
}
ฝั่ง Browser ยังเขียนง่ายอยู่
async function startLongPolling () {
    await fetchDataLongPolling()
    startLongPolling()
}

fetchDataShortPolling() // ดึง data มาแสดงตอนเปิดเว็บก่อน
startLongPolling() // หลังจากนั้นค่อยใช้ long Polling รอ data ใหม่
ปัญหาของ Long Polling แบบนี้คือ ถ้า data เปลี่ยนระหว่าง request เราจะไม่ได้ data นั้น เช่น
           poll  poll
Client: ----|-----|-----
Server: -----|--|--|----
             1  2  wait
จะเห็นว่าถ้า data เปลี่ยนจาก 1 เป็น 2 ระหว่างที่ไม่ได้ poll อยู่ client จะไม่ได้ data
วิธีแก้คือ อาจจะต้องส่ง token (เช่น timestamp) ของ data ล่าสุดไป แล้วเวลา poll ให้เอา token นั้นส่งกลับไปหา server

Web Socket

Web Socket เป็น full-duplex communication คือ client กับ server สามารถส่งข้อมูลไปกลับได้ผ่าน connection เดียว

ข้อดีของ Web Socket

  • ประหยัด resource ไม่มี overhead ของ http, เปิด connection ทิ้งไว้ สามารถส่ง data ไปกลับได้
  • มี library ครบ แทบทุกภาษา

ข้อเสียของ Web Socket

  • ทำงานบน Layer 4 (TCP) ถ้ามี middleware ที่ไม่รองรับ HTTP Upgrade ก็จะไม่สามารถใช้ได้ (เช่น Cloud Run)
  • มีโอกาสโดน timeout ถ้า set middleware ไม่ดี (เช่น reverse proxy, load balancer, cdn)

Server-Sent Event (SSE)

คล้าย ๆ Web Socket แต่ server push data หา client ได้ทางเดียว

ข้อดีของ Server-Sent Event

  • รันที่ไหนก็ได้ เพราะทำงานบน HTTP ธรรมดา
  • ประหยัด resource กว่า Short/Long Polling
  • เขียนง่าย ไม่ต้องมี library

ข้อเสียของ Server-Sent Event

  • IE ใช้ไม่ได้ 🤨
  • ถ้าเขียนเองต้องมีความรู้เรื่อง HTTP ในระดับนึง

ตัวอย่างการทำ Server-Sent Event

หน้าตาของ sse event
: บรรทัดที่ขึ้นต้นด้วย : คือ comment

: เราสามารถกำหนดชื่อ event ได้
event: add
data: 1

event: remove
data: 1

: ถ้าไม่กำหนดชื่อ event default คือ event: message
data: hello, sse

: ถ้า data มี 2 บรรทัด
data: hello,
data: sse
Server เขียนไม่ยากมาก แต่ต้องรู้ว่าจะส่งข้อมูลยังไง
func getDataWithSSESupport(w http.ResponseWriter, r *http.Request) {
    // ถ้าไม่ส่ง ?sse=1 มา ถือว่ายิงมาแบบ api ธรรมดา
    // จะได้ทำ api เดียว รองรับทั้ง api ธรรมดา ทั้ง sse
    if r.URL.Query().Get("sse") != "1" {
        getDataShortPolling(w, r)
        return
    }

    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache") // ไม่ให้ cache
    w.WriteHeader(http.StatusOK)

    for {
        data := getData()
        fmt.Fprintf(w, "data: %s\n\n", data)
        w.(http.Flusher).Flush()

        select {
        case <-untilNewDataReceived():
        case <-r.Context().Done():
            return
        }
    }
}
Browser ยิ่งง่าย
const source = new EventSource('/data')

source.addEventListener('add', (ev) => {
    //
})

source.addEventListener('remove', (ev) => {
    //
})

source.addEventListener('message', (ev) => {
    //
})

// หรือจะรับ ทุก event มาเลย
source.onmessage = (ev) => {
  console.log(ev)
}
ลองเอาไปรันเล่น ๆ
package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {
	http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			w.Write([]byte(`<!doctype html>
<div id=app></div>
<script>
const $app = document.getElementById('app')
const source = new EventSource('/data')
source.addEventListener('message', (ev) => {
	console.log(ev)
    app.innerHTML += ev.data + "<br>"
})
</script>`))
		}

		if r.URL.Path == "/data" {
			w.Header().Set("Content-Type", "text/event-stream")
			w.Header().Set("Cache-Control", "no-cache")
			w.WriteHeader(http.StatusOK)

			for {
				data := time.Now().Format(time.RFC3339)
				fmt.Fprintf(w, "data: %s\n\n", data)
				w.(http.Flusher).Flush()

				select {
				case <-time.After(time.Second):
				case <-r.Context().Done():
					return
				}
			}
		}
	}))
}

สรุป

  • ใช้ Server-Sent Event 🙈🙈🙈🙈🙈🙈
นอกจาก
  • ไม่อยากแก้ code server
  • คนใช้ไม่เยอะ
  • data ไม่จำเป็นต้อง realtime มาก
ให้ใช้ Short Polling

Note

  • Web socket ค่อนข้างมีปัญหาเวลา deploy เพราะบางที reverse proxy ไม่ support http upgrade แต่ library บางตัวจะ fallback ไปใช้ short/long Polling มีโอกาสที่ server จะโดน request ยิงรัว ๆ ยิ่งถ้าใช้ service ที่คิดตังตามจำนวน request ก็จะโดนค่าตรงนี้เยอะมาก อาจจะหลายล้าน request ต่อวัน
  • ถ้าจะ support IE ค่อยใช้ Long Polling ถ้าไม่ support IE ไปใช้ SSE ดีกว่า
  • หรือจะทำ Short Polling ไปก่อนก็ได้ แล้วคนใช้เยอะค่อยให้ api เดิม support SSE แก้ code เพิ่มไม่เยอะ
ที่มา https://acoshift.me/2019/0012-realtime-web.html

สรุป CORS แบบสั้น ๆ

สรุป CORS แบบสั้น ๆ

CORS ย่อมาจาก Cross-Origin Resource Sharing
Origin คือ header ตัวนึง ที่บอกว่า request ถูกเรียกจากที่ไหน จะต่างกับ Referer โดยที่ Origin จะไม่มี path มาด้วย
Origin ประกอบด้วย 3 ส่วน คือ
  1. Scheme เช่น http, https
  2. Hostname เช่น example.com, www.example.com
  3. Port เช่น 3000, 8000, 8080, 9000
ถ้าเข้าเว็บจาก file:// Origin จะเป็น null
Origin: null
Origin: <scheme>://<hostname>[:<port>]

Origin ที่ต่างกัน

  • http://example.com กับ https://example.com เป็นคนละ Origin กัน เพราะ scheme ไม่เหมือนกัน คือ http กับ https
  • http://example.com กับ http://www.example.com เป็นคนละ Origin กัน เพราะ hostname ไม่เหมือนกัน คือ example.com กับ www.example.com
  • http://example.com:3000 กับ http://example.com:8080 เป็นคนละ Origin กัน เพราะ port ไม่เหมือนกัน คือ 3000 กับ 8000
  • http://example.com กับ http://example.com:80 เป็น Origin เดียวกัน เพราะ http ถ้าไม่มี port จะถือว่าเป็น port 80 RFC 6454 3.2.1

เมื่อเกิด CORS

เมื่อเราต้องการ fetch resource จาก origin นึง ไปอีก origin นึง จะเกิดการเรียก resource ข้าม origin กัน
Browser จะส่ง OPTIONS request ไปถาม server อีก origin ก่อน ว่าจะอนุญาตให้ origin ต้นทางสามารถเข้าไปเรียก resource ได้ไหม
หมายความว่า ถ้า browser ไม่ support CORS ก็จะสามารถเรียกได้เลย โดยไม่ติด CORS หรือ request เกิดจาก Server-Server คุยกัน ก็จะไม่มี CORS เพราะ คนส่ง CORS คือ Browser
ตัวอย่างเช่น
ถ้าเราอยู่ที่ https://example.com แล้วต้องการส่ง request ไปที่ https://api.example.com
fetch('https://api.example.com/api1', {
    method: 'POST',
    credentials: 'include',
    headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': 'key-1'
    },
    body: JSON.stringify(data)
})
สิ่งที่เกิดขึ้นคือ Browser จะส่ง preflight request ไปถามที่ api.example.com ก่อนว่า
  • จะให้ Origin https://example.com ส่ง request ไปไหม ?
  • จะให้ส่ง Method POST ไปไหม ?
  • จะให้ส่ง Cookie (credentials: 'include') ไปไหม ?
  • จะให้ส่ง Header Content-Type กับ X-Api-Key ไปไหม ?
OPTIONS /api1 HTTP/1.1
Host: api.example.com
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Api-Key

ถ้า Server เราจะต้องการจะตอบ Browser ว่า
  • ที่ path นี้ ให้ Origin https://example.com ส่ง request เข้ามาได้
  • ที่ path นี้ ให้ส่ง Method GET กับ POST เข้ามาได้
  • ที่ path นี้ ให้ JavaScript ส่ง Header Content-Type กับ X-Api-Key เข้ามาได้
  • ที่ path นี้ ให้ส่ง Cookie เข้ามาได้
  • ที่ path นี้ ให้ Browser จำคำตอบข้างบนไว้ 3600 วินาที จะได้ไม่ต้องมาถามซ้ำ
Server จะต้องตอบ Browser กลับไปว่า
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, X-Api-Key
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
Vary: Origin

Vary ใช้ในการบอก Reverse Proxy (Cache, CDN) ว่า ถ้าจะ cache response นี้ ให้ cache แยกตาม Origin เช่นเราอนุญาตให้ example.com เข้าได้ แต่ไม่ให้ evil.com เข้า แต่ถ้าเราไม่บอก CDN ว่าถ้าจะ cache ให้ cache แยกตาม Origin ตอน evil.com เข้ามา อาจจะได้ผลลัพท์ของ example.com กลับไป
ถ้าเราไม่อยากให้ Browser ส่ง request เข้ามาหล่ะ ?
ก็แค่ตอบ error อะไรกลับไปก็ได้ เช่น
HTTP/1.1 403 Forbidden
Content-Type: text/plain
Content-Length: 9

Forbidden
นอกจากนี้เมื่อ Browser ส่ง Request จริงเข้ามา เราจะยังต้องตอบ browser บางคำตอบซ้ำ
เช่น หลังจากที่ Browser ได้คำตอบว่า api.example.com อนุญาตให้ https://example.com ส่ง request เข้ามาได้
Browser จะส่ง Request ที่เราเขียนใน JavaScript เข้ามาว่า
POST /api1 HTTP/1.1
Host: api.example.com
Origin: https://example.com
Cookie: sess=1234
Content-Length: 16
Content-Type: application/json
X-Api-Key: key-1

{"text":"hello"}
เราจะต้องแนบ header กลับไปบอก browser ต่อว่า
  • ให้ https://example.com อ่านนะ เผื่อ response ของอีก Origin ถูก cache ไว้ browser จะไม่ส่ง response นี้ไปให้ JavaScript ถึงแม้ว่าเรา inspect ดูแล้วจะได้ success ก็ตาม
  • ให้ส่ง Cookie เข้ามาได้ (ถึงแม้ว่า Server จะประมวลผลทุกอย่างเสร็จแล้ว แต่ถ้า Browser ส่ง Cookie ไป แล้วไม่เห็น header นี้ใน response อีกรอบ ก็จะไม่ส่ง response ไปให้ JavaScript)
  • ผลลัพท์นี้ ให้ JavaScript อ่าน Header X-Request-Id ได้
    ถ้าเราไม่บอก Browser ถึงแม้เราจะส่ง header นี้กลับไปใน response แต่เราจะไม่สามารถเขียน JavaScript เข้าไปอ่าน header ตัวนี้ได้
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-Id
X-Request-Id: req-123
Vary: Origin
Content-Type: application/json
Content-Length: 11

{"ok":true}

สำคัญมาก ๆ กับการ Allow all origin

บางครั้งเราต้องการให้ทุก Origin เข้ามาได้ เราสามารถใส่
Access-Control-Allow-Origin: *
แต่เราจะ ไม่ สามารถทำแบบนี้ได้
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
เพราะถ้าเรา login ที่เว็บ https://bank.com แล้วทุกเว็บจะสามารถส่ง request มาโอนเงินออกจากบัญชีเราได้หมดเลย เพราะทุกเว็บที่ส่ง request มาที่ api ของ bank.com browser จะแนบ Cookie ที่ได้ตอน login ที่เว็บ bank.com มาด้วย
บางคนอยากให้ทุกเว็บส่ง Cookie เข้ามาได้ ก็ต้องลักไก่ เอา Origin ที่มาจาก request ใส่เข้าไปใน Allow Origin แทน แต่ต้องคิดดี ๆ ก่อนทำ ไม่ฉะนั้นทุกเว็บจะได้ Cookie ไปใช้ได้เลย

สรุป

CORS คือสิ่งที่ Browser คุยกับ Server เพื่อถามว่า
  • จะให้ JavaScript ที่อยู่คนละ Origin กับ Server ส่ง Request ไปหาได้ไหม
  • ถ้าได้ จะให้ JavaScript ส่งอะไรไปหา Server บ้าง
  • แล้ว Response ที่ Server ส่งกลับมา จะให้ JavaScript อ่านอะไรได้บ้าง

References

Set MongoDB in the windows path environment

  Let’s set MongoDB in the windows environment in just a few steps. Step 1: First download a suitable MongoDB version according to your mach...