วันจันทร์ที่ 17 กุมภาพันธ์ พ.ศ. 2557

เขียน Windows Services ด้วย Golang

ช่วงปีหลัง หนีไม่พ้นต้องเขียนอะไรที่เกี่ยวกับ Windows อย่างหลีกเลี่ยงไม่ได้ พอจะตัดสินใจใช้ Golang คำถามที่ตามมาคือแล้วถ้าต้องเขียน services ล่ะ ใช้ golang ได้รึเปล่า

คำตอบวันนี้คือ "ได้"

ผมเอา code จาก RESTful ของ Blog ที่แล้วมาใช้ทำ service โดยการ compile code บน windows ให้เป็น .exe ก่อน

> go build restmain.go

จะได้ restmain.exe จากนั้นผมใช้ NSSM - the Non-Sucking Service Manager (มาพร้อม source code และ execute binary) unzip แล้ว

> nssm McDuckService [path to]restmain.exe

เรียบร้อยลองเปิด service management อ่า... McDuckService มาแล้ว Double Click แล้ว Start เลย... 


[เพิ่มเติม]

  • เวลาจะลบ service ออกใช้
    • > sc delete McDuckService
  • command line ที่เกียวกับ service ต้องเป็น administrator right 
  • อ้างอิง http://sanatgersappa.blogspot.com/2013/07/windows-service-with-go-easy-way.html
    • เขียนก่อนผมเกือบปี ใช้ nssm เหมือนกันเลย





วันศุกร์ที่ 14 กุมภาพันธ์ พ.ศ. 2557

ลองเขียน REST API ด้วย Go (with framework)

ก่อนนี้ลองเขียน web application ด้วย pure Go มาแล้ว และได้เห็นพิษสงของ Go พอสมควร คราวนี้ขอลอง REST API สักหน่อย แต่อยากได้แบบ ง่ายมากๆ ประมาณ KISS เลย (Keep It Siimple & Studip) เลยขอลองใช้ Framework ในตลาดดูบ้าง

ผมเลือก http://github.com/ant0ine/go-json-rest มาลองเล่นเป็นตัวแรก เริ่มเลย...

สร้างไฟล์ชื่อ restmain.go (หรืออะไรก็ได้)
แทรก import ก่อน

import (
   "net/http"
   "github.com/ant0ine/go-json-rest"
)

แล้วสร้าง func main

func main() {
handler := rest.ResourceHandler{}
handler.SetRoutes(
rest.Route{"Get", "/users/:id", GetUser},
)
http.ListenAndServe(":8080", &handler)
}

อธิบายได้ว่า ประกาศ handler ด้าย rest ของ go-json-rest แล้ว SetRoutes ไปที่ url ที่เราต้องการ จากนั้นยัดเข้าท่ามาตรฐานของ net/http package ของ Go

ไปดูที่ handler กัน

func GetUser(w *rest.ResponseWriter, req *rest.Request) {
user := User{
Id:   req.PathParam("id"),
Name: "McDuck",
}
w.WriteJson(&user)
}

ทดลองง่ายๆ ด้วยการส่งอะไรมา (ผ่าน url parameter) ก็จัดการส่งกลับ (ด้วย json format) เราก็ต้องสร้าง struct สำหรับการเก็บข้อมูลก่อนแปลงเป็น json

type User struct {
Id   string
Name string
}

แล้วก็ assign (:=) user โดยมี Id = ค่า id ที่ส่งผ่านมาทาง url parameter และ name (อันนี้ใส่เอง) จากนั้นใช้ res.ResponseWriter เขียน Json กลับไปที่ resquester

ก่อน run set GOPATH ก่อน (path เดียวกับ ที่เราเขียน code ด้านบน...)
$ export GOPATH=`pwd`

(windows platform ไปหาทางเอาเอง นาทีนี้กำลังงอล Windows,  Hang อยู่ได้ แม่ม...)

จากนั้นเรียก
$ go get .
go จะ download source มาจาก github.com/ant0ine/go-json-rest ไว้ที่ src ให้เรา จากนั้น
$ go run restmain.go

แล้วเปิด browser ไปที่ http://localhost:8080/users/1001100
Result :

{
  "Id": "10010",
  "Name": "McDuck"
}

[ง่ายดีจังตังค์อยู่ครบ]

source restmain.go

//-------------------------------------------------------------
package main

import (
 "github.com/ant0ine/go-json-rest"
 "net/http"
)

type User struct {
 Id   string
 Name string
}

func GetUser(w *rest.ResponseWriter, req *rest.Request) {
 user := User{
  Id:   req.PathParam("id"),
  Name: "McDuck",
 }
 w.WriteJson(&user)
}

func main() {
 handler := rest.ResourceHandler{}
 handler.SetRoutes(
  rest.Route{"Get", "/users/:id", GetUser},
 )
 http.ListenAndServe(":8080", &handler)
}

//-------------------------------------------------------------

วันพุธที่ 12 กุมภาพันธ์ พ.ศ. 2557

Tough Week : Oracle ODBC บน Linux

เป็นอาทิตย์ที่ยากลำบากมากช่วงหนึ่งแต่ก็ผ่านมาได้ จนต้อง blog ไว้เผื่อมีประโยชน์กับ ตัวเองและคนอื่น :)

กว่าสองเดือนที่ผมได้เขียน Server สำหรับการทำ Semi-Realtime transaction processing server (ตั้งชื่อให้มันยากๆ ไปงั้นแหละ) โดยใช้ Erlang ในการพัฒนา โจทย์ก็คือต้องเก็บ transaction ที่ส่งผ่านเข้ามาเป็น XML จากนั้น Encode XML เก็บเข้า Oracle แล้ว Archive XML ไว้ใน Document Base สักตัว (ผมเลือกใช้ MongoDB)

แรกๆ ใช้ Erlang OCI (Oracle Call Interface) ก็ดูดี แต่พบว่า มีปัญหาหลายส่วน จึงตัดสินใจเปลี่ยนจากการ code โดยการ generate SQL statement ใน Erlang แล้วยิง Statement ผ่าน OCI มาเป็นการเรียกน Stored Procedure ผ่าน ODBC แทน

Code เสร็จแล้ว ถึงเวลาต้อง Deploy แล้ว... ผมเลือก Debian 7.4 ที่ต้องติดตั้งเพิ่มก็มี

  • Erlang (ใช้ R16B03-1) ติดตั้ง package เพิ่มเติมตามนี้
    • sudo apt-get install build-essential libncurses5-dev m4
    • sudo apt-get install openssl libssl-dev
    • sudo apt-get install unixodbc-dev
  • git
  • mongodb (ใช้ repo ของ 10 gen : http://docs.mongodb.org/manual/tutorial/install-mongodb-on-debian/ )
  • และ ODBC 
ปัญหาก็คือ Instance Client ของ Oracle (download จาก http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html ) support .rpm ผมใช้ alien แปลง .rpm เป็น .deb ซึ่งก็ติดตั้งได้ แต่ไม่สามารถ connect กับ server ได้ เลยเปลี่ยนไปจาก Debian  ไปเป็น CentOS แทน เพราะไม่รู้ว่า ปัญหาจริงๆ คืออะไร

ย้ายบ้านมา CentOS (ขั่วคราว)
ข้อดีของ VMWare คือตรงนี้แหละ อยากลอง Distro ไหนง่ายนิดเดียว
ติดตั้ง CentOS เสร็จ ไม่ต้องคิดมาก download rpm จาก Oracle's instance-client site page มาเลย ไม่ต้องเยอะเอาแค่
  • oracle-instantclient12.1-basic-12.1.0.1.0-1.x86_64.rpm 
  • oracle-instantclient12.1-sqlplus-12.1.0.1.0-1.x86_64.rpm
  • instantclient-odbc-linux.x64-12.1.0.1.0.zip 
แล้ว $sudo rpm -ivh oracle-instanceclient12.1-basic-12.1.0.1.0-1.x86_64.rpm ไปก่อน ตากด้วย $unzip  instantclient-odbc-linux.x64-12.1.0.1.0.zip แล้ว copy libsqora.so.12.1 ที่ได้จากการ unzip ไปไว้ที่ /usr/lib/oracle/12.1/client64/lib

จากนั้นติดตั้ง unixODBC 
$sudo yum install unixODBC

export environment 

$ export ORACLE_HOME=/usr/lib/oracle/12.1/client64
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib

$ export TWO_TASK=//xxx.xxx.xxx.xxx:1521/listener
(note : listener ต้องตรงกับ server นะครับ)

note : ผมสร้าง oracle.conf ที่มี 
/usr/lib/oracle/12.1/client64/lib
หนึ่งบรรทัด ไว้ที่ /etc/ld.so.conf.d แล้วเรียก 
$ sudo ldconfig
หนึ่งที ด้วย :D

จากนั้นก็ไป copy tnsnames.ora มาจาก server (server ผมเป็น windows 7) เอามาจาก c:/app/[username]/product/11.2.0/dbhome_1/network/admin (ลองไล่ๆ ดูนะครับ แต่ละเครื่องไม่เหมือนกัน อยู่ที่การติดตั้ง)
ผมแก้ tnsnames.ora นิดหน่อยครับ เหลือแต่ 


_SID_NAME_ =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)
        (HOST = xxx.xxx.xxx.xxx)
        (PORT = 1521)
      )
    )
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = _SID_NAME_)
    )
  )

_SID_NAME_ ตรงกับ SID ที่ server 
ไปตามอ่านใน oracle document พบว่าให้สร้าง tnsnames.ora ไว้ที่ $ORACLE_HOME/network/admin ก็ไปสร้างไว้ตรงนั้นตามเอกสาร 

จากนั้นสร้าง ODBC's drive profile ใน /etc/odbcinst.ini
ตามนี้ครับ 

[OracleODBC-12g]
Description = Oracle ODBC driver for Oracle 12g
Driver64 = /usr/lib/oracle/12.1/client64/lib/libsqora.so.12.1
FileUsage = 1
Driver Logging = 7

เน้นนะครับ : Driver64 เพราะผมลง x86_64 ถ้า google จะเจอแต่ Driver เฉยๆ (งมตั้งนาน) 
แล้วชี้ไปที่ libsqora.so.12.1 ที่ได้จากตอนที่เรา unzip ตอนต้นๆ

จากนั้นสร้าง DSN ใน /etc/odbc.ini ตามนี้ครับ

[simple]
Driver = OracleODBC-12g
DSN = OracleODBC-12g
ServerName = _SID_NAME_
UserID = _USER_
Password = _PASSWORD_

Note : Driver ต้องตรงกับที่เรากำหนดไว้ใน odbcinst.ini, ServerName ตรงกับ _SID_NAME_ ใน tnsnames.ora ส่วน _USER_ _PASSWORD_ ก็ตรงกับ oracle server ที่เราตั้งไว้

ทดสอบ....
ทดสอบ DSN ที่ส้รางขึ้นด้วย 
$ isql simple
simple คือชื่อ DSN ที่เราสร้างขึ้นใน odbc.ini

ผลลัพท์
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+

ผมเจอ error ไม่ต่ำกว่า 4-5 ตัว (แต่ละตัวทำเอามึน) เช่น
Error ORA-12154 :  TNS:could not resolve the connect identifier specified
อันนี้ตำแหน่ง tnsnames.ora อยู่ผิดที่ 

Debug ด้วย Log
หากพบปัญหาตอนเรียก isql ให้ใส่  -v เพื่อดู ORA error number แล้ว google error number ดู ถ้ายังไม่ได้อีก ผมใช้ strace ตามนี้

$ strace -fac -o isql.log isql -v simple

เช่นตอนกำหนดตำแหน่ง tnsnames.ora ผิด ผมเปิด isql.log พบว่า นั่นไง by default isql จะเปิด tnsnames.ora จาก 
"/home/[user]/.tnsnames.ora"
"/etc/tnsnames.ora"
"/usr/lib/oracle/12.1/client64/lib/network/admin/tnsnames.ora"
เป็นต้น

การใช้ sqlplus64 ตามด้วย custom tns connect ORA server ได้ ไม่ได้หมายความว่า ODBC จะ connect ได้ เช่น 
$ sqlplus64 user/password@//XXX.XXX.XXX.XXX/_SID_NAME_
connect ได้ query ได้ แต่ isql ไม่ works

การติดตั้ง sqleveloper Connect ORA server ได้ ไม่ได้หมายความว่า ODBC จะ connect ได้ เช่นกัน

อาจไม่ cover ทุก error ที่เจอ แต่ใคร install Oracle ODBC Driver บน Linux แล้วพบปัญหาลองแลกเปลี่ยนกันดูครับ 

ผมยังใหม่กับ Oracle มาก :D

วันศุกร์ที่ 24 มกราคม พ.ศ. 2557

ISO8601 TimeZone กับ Oracle PLSQL

ตัดสินใจส่ง XML เข้าไป Parse ใน Oracle procedure แทนที่จะ Parse ด้วย Erlang แล้วส่งไป ซึ่งก็น่าจะดีกว่า (หันไปเขียน PL อย่างเดียว)

แต่พอ Extract XML มาก็เจอปัญหา ISO8601 ที่ก่อนนี้ผมใช้ Erlang iso8601 library parse ออกมาแล้ว convert เป็น Oracle DateTime format ก่อนนำไปยัดเข้า Statement แล้วส่งผ่าน ODBC/OCI

แต่พอเป็น PL เหมือนต้องเรียนใหม่ (เหมือนเขียน Pascal เลยนะ) หาทาง Convert ISO8601 ที่ Generate มาจาก .net ไม่เจอ เพราะ format ที่มาเป็นแบบนี้

2013-08-30T14:26:58.1640094+07:00

ค่อยๆ ไล่ที่ละตัวจนได้ format

yyyy-mm-dd"T"hh24:mi:ss.ff7

ด้วย function TO_TIMESTAMP ของ PL

แต่ TimeZone นี่สิยังไม่ผ่าน อ่านในเอกสารพบว่า

ถ้าเป็น named timezone เช่น
CET : Central European Time
PST : Pacific Standard Time
ให้ใช้ TZR

ถ้าเป็น +/- เวลาเช่น
+07:00 ประเทศไทยเรา
ให้ใช้ TZH:TZM

แต่ลองใช้ยังไงก็เจอ ORA-01821 : date format not recognised

สุดท้าย พบว่า ต้องใช้ TO_TIMESTAMP_TZ ก็เสียเวลาปีสามชั่วโมงเต็มๆ

TO_TIMESTAMP_TZ('2013-08-30T14:26:58.1640094+07:00',  'yyyy-mm-dd"T"hh24:mi:ss.ff7TZH:TZM');


Erlang เป็นญาติกับ Prolog, ยังไง?

ก็เลยลอง Port Erlang มาเป็น Prolog สนุกๆ จะได้เห็นว่า จริงๆ แล้ว Erlang ไม่ไช่ภาษาที่คิดใหม่ทั้งหมดนะ

 Erlang
-module(mathStuff).
-export([factorial/1]).

factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).


Prolog
:- module(mathStuff).
:- export  factorial/1.

factorial(0, 1).
factorial(N, R) :-
R is R * factorial(N-1).


วันจันทร์ที่ 20 มกราคม พ.ศ. 2557

กับดัก iso8601 ของ .Net

งมกับ DateTime ทั้งวัน Insert Record เข้าไปแล้ว Data ก็เดียวกัน ทำไม Query กลับมาไม่เจอ ผีหลอก!!!

ตอน Insert นี่ผมใช้ Erlang parse iso8601 มาเป็น Tuple แล้วใช้ ErlSQL จัดการ statement โดย parse DateTime format ที่ได้มาจาก XML ที่สร้างจาก .NET ตัวอย่าง

2013-08-30T14:26:58.1620093+07:00

ISO8601 ใน Erlang Parse ออกมาได้เป็น {{2013,8,30},{7,26,58}} โดยเอา +7:00 มาคิด ลดเวลาให้เป็น GMT +0:00 ซึ่งก็น่าจะถูก 

เวลาที่ได้คือ 7:26:58

แต่ Code .NET หาไม่เจอ!!!! ฉิบหายละ!!!!

.NET อ่าน XML file เดียวกัน Data เดียวกัน แต่ไม่ใช้ +7:00 มาคำนวน จาก XML ได้เป็น 14:26:58 ไม่เอา +7:00 มาคิด!!!!

ถ้า Iso8601 ของ Erlang ถูก...ซึ่งผมต้อง port .NET service มาเป็น Erlang แล้วข้อมูลเดิมล่ะ...

ถ้า .NET ถูกคงไม่มีปัญหาอะไร เดี๋ยวทำ Erlang ให้ผิดตามก็ได้

พอเข้าไปดู http://www.timestampgenerator.com/
แล้วลอง Generate ISO8601 ดู อ้าว Erlang ถูกนะ งั้น .NET framework (2.0) ก็ Bug น่ะสิ... เวรละ.... 

หรือเพราะ .NET ฉลาด!!! ดู Local time จาก Control Panel แล้วเทียบเวลา โดยไม่แปลง เวลาให้ลดลง 7 ชม. (ให้เป็น GMT) เพราะเข้าใจว่า มันคือ Local time zone

แล้วเวลาที่ได้มา 14:26:58 มันคือการบันทึกตอนไหน? ตอน 7:26:58 หรือ ตอน 14:26:58 

ต้องไปหาที่มาของเวลาแล้วค่อยกลับมาหาว่า ควรจะปรับใครยังไงอีกที

ไงละ แค่ DateTime :'(

วันอาทิตย์ที่ 19 มกราคม พ.ศ. 2557

Copy Code ข้ามเครื่อง แบบ Quick and Dirty

ช่วงนี้เป็นฤดู port code จาก Windows Service ที่เขียนด้วย .net มาเป็น Erlang แล้ว code ส่วนมากจะเป็น SQL Statement ที่เหมือนกัน ไม่ว่าจะเขียนด้วยภาษาอะไร พอโครงสร้างที่เขียนด้วย Erlang เสร็จ เช่นพวก Supervisor ไหนจะดูแล Process ไหน Process ไหนเป็น dynamic เป็น static ตรงไหนใช้ OTP บ้าง ครบหมด ถึงคิวต้อง port SQL Statement แล้ว

ซึ่งการ port (สำหรับผม) ไม่ใช่ว่า อ่าน code แล้วเขียนตามได้เลย อย่างน้อยๆ มันก็ code คนละประเภทกัน (OOP vs Functional) แต่สิ่งที่ต้อง port มาให้เหมือนๆ กันเลยก็คือ PL/SQL statement สำหรับ Oracle

ผมไม่ได้ดูแค่ตา เนื่องจากไม่ได้เขียน C# เลย ตอน Microsoft ออก C# ก็ปฏิเสธมาตลอด (อยู่แต่กับ C/C++) พอจะต้อง port C# ก็เลยต้องอาศัย Visual Studio (2005 อีกต่างหาก) debug ไป F9, F10, F11, Shift-F11 ไป เจอ code ก็ว่ากันไป

ทีนี้ถึงเวลาต้อง Copy SQL statement มาอีกเครื่อง เลยต้องหาทางที่สะดวกที่สุด เพราะ C# code ต้อง debug อยู่บน Windows7+Visual Studio 2005 ส่วน Erlang code อยู่บน Mac OSX

แต่ก่อน ส่ง message ระหว่างเครื่องผมใช้ MSN บ้าง Jabber บ้าง แต่ตอนนี้ไม่มี MSN ส่วน Jabber เสียเวลาสร้าง account อีก Google account ก็เป็น Hangouts หมด งง ใช้ไม่เป็น เสียเวลา ช้า

ค้นไปเจอ IP Messenger เข้า น่าสนใจครับ เล็ก เร็ว สะดวก ไม่ต้องมาสร้าง account วุ่นวาย Open Source ด้วย

บน Windows : http://www.ipmsg.org/index.html.en
บน OSX : http://ishwt.net/en/software/ipmsg/

ง่ายดี ไม่ต้องคิดมาก แถมมี code เผื่อเอาไปทำอะไรต่อยอดได้อีก