diff options
| author | Shulhan <m.shulhan@gmail.com> | 2020-04-12 04:35:58 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2020-04-12 04:35:58 +0700 |
| commit | 9cf57fc952e4161c8e7fc5c9de8869d3137acb20 (patch) | |
| tree | 1fe91b6316ea34ff6600878f7d12b474da290361 | |
| parent | c6f1ebdc315eda5f41fd7a3f30c693b243882be8 (diff) | |
| download | kamusku-9cf57fc952e4161c8e7fc5c9de8869d3137acb20.tar.xz | |
all: refactoring client to load cookies automatically
When creating new client, the directClient will autoload cookies in
predefined location.
If Login is called, the cookies will autosave directly to predefined
location.
This will allow the server to use authenticated directClient to mitigate
the limit on KBBI official server.
| -rw-r--r-- | client.go | 40 | ||||
| -rw-r--r-- | cmd/kbbi/main.go | 74 | ||||
| -rw-r--r-- | direct_client.go | 120 | ||||
| -rw-r--r-- | direct_client_test.go | 2 | ||||
| -rw-r--r-- | server.go | 16 |
5 files changed, 137 insertions, 115 deletions
@@ -5,7 +5,7 @@ package kbbi import ( - "net/http" + "fmt" ) // @@ -19,19 +19,18 @@ type Client struct { // // NewClient create and initialize new client. -// If cookies is not empty, the direct client will be initialized and actived. // -func NewClient(cookies []*http.Cookie) (cl *Client, err error) { +func NewClient() (cl *Client, err error) { cl = &Client{} - cl.direct, err = newDirectClient(cookies) + cl.direct, err = newDirectClient() if err != nil { return nil, err } cl.api = newAPIClient("") - if cookies != nil { + if cl.IsAuthenticated() { cl.active = cl.direct } else { cl.active = cl.api @@ -59,6 +58,14 @@ func (cl *Client) CariDefinisi(words []string) ( } // +// IsAuthenticated will return true if client has logged in to KBBI official +// server. +// +func (cl *Client) IsAuthenticated() bool { + return cl.direct.isAuthenticated() +} + +// // ListKataDasar list all of the root words in dictionary. // func (cl *Client) ListKataDasar() (res DaftarKata, err error) { @@ -78,26 +85,13 @@ func (cl *Client) ListKataDasar() (res DaftarKata, err error) { // Login authenticate the client using username and password to official KBBI // server. // -func (cl *Client) Login(user, pass string) (cookies []*http.Cookie, err error) { - if cl.direct == nil { - cl.direct, err = newDirectClient(nil) - if err != nil { - return nil, err - } - } - cookies, err = cl.direct.login(user, pass) +func (cl *Client) Login(user, pass string) (err error) { + err = cl.direct.login(user, pass) if err != nil { - return nil, err + return fmt.Errorf("Login: %w", err) } + cl.active = cl.direct - return cookies, nil -} -// -// SetCookies for HTTP request in direct client. -// -func (cl *Client) SetCookies(cookies []*http.Cookie) { - if cl.direct != nil { - cl.direct.setCookies(cookies) - } + return nil } diff --git a/cmd/kbbi/main.go b/cmd/kbbi/main.go index adba621..3b07c36 100644 --- a/cmd/kbbi/main.go +++ b/cmd/kbbi/main.go @@ -2,19 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +// Program kbbi adalah antar-muka perintah untuk Kamus Besar Bahasa Indonesia +// (KBBI) menggunakan API. +// package main import ( - "bytes" - "encoding/gob" - "errors" "flag" "fmt" - "io/ioutil" "log" - "net/http" - "os" - "path/filepath" "sort" "github.com/shuLhan/kbbi" @@ -34,14 +31,6 @@ func main() { log.SetFlags(0) - baseDir, err := os.UserConfigDir() - if err != nil { - log.Fatal("kbbi.createConfigDir:", err) - } - - createConfigDir(baseDir) - cookies := loadCookies(baseDir) - flag.StringVar(&surel, "surel", "", "Nama pengguna") flag.StringVar(&sandi, "sandi", "", "Sandi pengguna") flag.BoolVar(&isListKataDasar, "daftar-kata-dasar", false, @@ -49,25 +38,22 @@ func main() { flag.Parse() - cl, err := kbbi.NewClient(cookies) + cl, err := kbbi.NewClient() if err != nil { log.Fatal(err) } if len(surel) > 0 && len(sandi) > 0 { - cookies, err = cl.Login(surel, sandi) + err = cl.Login(surel, sandi) if err != nil { log.Fatal("kbbi: ", err) } - saveCookies(baseDir, cookies) - cl.SetCookies(cookies) } if isListKataDasar { - if len(cookies) == 0 { + if cl.IsAuthenticated() { log.Fatal("opsi -daftar-kata-dasar membutuhkan opsi -surel dan -sandi") } - listKataDasar(cl) return } @@ -106,13 +92,6 @@ func main() { } } -func createConfigDir(baseDir string) { - err := os.MkdirAll(filepath.Join(baseDir, configDir), 0700) - if err != nil { - log.Fatal("kbbi.createConfigDir:", err) - } -} - func listKataDasar(cl *kbbi.Client) { kataDasar, err := cl.ListKataDasar() if err != nil { @@ -131,42 +110,3 @@ func listKataDasar(cl *kbbi.Client) { fmt.Println(kata) } } - -func loadCookies(baseDir string) (cookies []*http.Cookie) { - f := filepath.Join(baseDir, configDir, cookieFile) - - _, err := os.Stat(f) - if errors.Is(err, os.ErrNotExist) { - return nil - } - - body, err := ioutil.ReadFile(f) - if err != nil { - log.Fatal("kbbi.loadCookies: ", err) - } - - dec := gob.NewDecoder(bytes.NewReader(body)) - - err = dec.Decode(&cookies) - if err != nil { - log.Println("kbbi.loadCookies: ", err) - } - - return cookies -} - -func saveCookies(baseDir string, cookies []*http.Cookie) { - f := filepath.Join(baseDir, configDir, cookieFile) - - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(cookies) - if err != nil { - log.Fatal("kbbi.saveCookies: ", err) - } - - err = ioutil.WriteFile(f, buf.Bytes(), 0600) - if err != nil { - log.Fatal("kbbi.saveCookies: ", err) - } -} diff --git a/direct_client.go b/direct_client.go index 75f1404..aa296b5 100644 --- a/direct_client.go +++ b/direct_client.go @@ -6,12 +6,16 @@ package kbbi import ( "bytes" + "encoding/gob" + "errors" "fmt" "io/ioutil" "log" "net/http" "net/http/cookiejar" "net/url" + "os" + "path/filepath" "strconv" "strings" @@ -21,6 +25,8 @@ import ( ) const ( + cookieFile = "cookie" + configDir = "kbbi" maxPageNumber = 501 ) @@ -28,7 +34,9 @@ const ( // directClient for KBBI web using HTTP. // type directClient struct { + baseDir string cookieURL *url.URL + cookies []*http.Cookie httpc *http.Client } @@ -36,7 +44,7 @@ type directClient struct { // newDirectClient create and initialize new client that connect directly to // KBBI official website. // -func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { +func newDirectClient() (cl *directClient, err error) { cookieURL, err := url.Parse(baseURL) if err != nil { return nil, fmt.Errorf("newDirectClient: %w", err) @@ -48,11 +56,7 @@ func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { jar, err := cookiejar.New(jarOpt) if err != nil { - return nil, err - } - - if cookies != nil { - jar.SetCookies(cookieURL, cookies) + return nil, fmt.Errorf("newDirectClient: %w", err) } cl = &directClient{ @@ -63,6 +67,15 @@ func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { }, } + err = cl.loadCookies() + if err != nil { + return nil, fmt.Errorf("newDirectClient: %w", err) + } + + if cl.cookies != nil { + jar.SetCookies(cookieURL, cl.cookies) + } + return cl, nil } @@ -169,20 +182,26 @@ func (cl *directClient) ListKataDasar() (kataDasar DaftarKata, err error) { } // -// Login authenticate the client using username and password. +// isAuthenticated will return true if the client already login; otherwise it +// will return false. // -func (cl *directClient) login(user, pass string) ( - cookies []*http.Cookie, err error, -) { +func (cl *directClient) isAuthenticated() bool { + return len(cl.cookies) > 0 +} + +// +// login authenticate the client using username and password. +// +func (cl *directClient) login(surel, sandi string) (err error) { tokenLogin, err := cl.preLogin() if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } params := url.Values{ paramNameRequestVerificationToken: []string{tokenLogin}, - paramNamePosel: []string{user}, - paramNameKataSandi: []string{pass}, + paramNamePosel: []string{surel}, + paramNameKataSandi: []string{sandi}, paramNameIngatSaya: []string{paramValueFalse}, } @@ -190,39 +209,39 @@ func (cl *directClient) login(user, pass string) ( req, err := http.NewRequest(http.MethodPost, loginURL, reqBody) if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } req.Header.Set(headerNameContentType, headerValueContentType) res, err := cl.httpc.Do(req) if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } defer res.Body.Close() resBody, err := ioutil.ReadAll(res.Body) if err != nil { - return nil, fmt.Errorf("Login: %w", err) + return fmt.Errorf("Login: %w", err) } if res.StatusCode >= http.StatusBadRequest { - return nil, fmt.Errorf("Login: %d %s", res.StatusCode, resBody) + return fmt.Errorf("login: %d %s", res.StatusCode, resBody) } - cookies = cl.httpc.Jar.Cookies(cl.cookieURL) + cl.cookies = cl.httpc.Jar.Cookies(cl.cookieURL) + cl.setCookies() + cl.saveCookies() - return cookies, nil + return nil } // // setCookies for HTTP request that need an authentication. // -func (cl *directClient) setCookies(cookies []*http.Cookie) { - if len(cookies) > 0 { - cl.httpc.Jar.SetCookies(cl.cookieURL, cookies) - } +func (cl *directClient) setCookies() { + cl.httpc.Jar.SetCookies(cl.cookieURL, cl.cookies) } func (cl *directClient) parseHTMLKataDasar(htmlBody []byte) ( @@ -309,3 +328,58 @@ func (cl *directClient) preLogin() (token string, err error) { return token, nil } + +// +// loadCookies load the KBBI cookies from file. +// +func (cl *directClient) loadCookies() (err error) { + cl.baseDir, err = os.UserConfigDir() + if err != nil { + return fmt.Errorf("loadCookies: %w", err) + } + + f := filepath.Join(cl.baseDir, configDir, cookieFile) + + _, err = os.Stat(f) + if errors.Is(err, os.ErrNotExist) { + return nil + } + + body, err := ioutil.ReadFile(f) + if err != nil { + return fmt.Errorf("loadCookies: %w", err) + } + + dec := gob.NewDecoder(bytes.NewReader(body)) + + err = dec.Decode(&cl.cookies) + if err != nil { + return fmt.Errorf("loadCookies: %w", err) + } + + return nil +} + +// +// saveCookies store the client cookies to the file for future use. +// +func (cl *directClient) saveCookies() { + err := os.MkdirAll(filepath.Join(cl.baseDir, configDir), 0700) + if err != nil { + log.Println("saveCookies:", err) + } + + f := filepath.Join(cl.baseDir, configDir, cookieFile) + + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err = enc.Encode(cl.cookies) + if err != nil { + log.Println("saveCookies: ", err) + } + + err = ioutil.WriteFile(f, buf.Bytes(), 0600) + if err != nil { + log.Println("saveCookies: ", err) + } +} diff --git a/direct_client_test.go b/direct_client_test.go index 894b876..dd3b59b 100644 --- a/direct_client_test.go +++ b/direct_client_test.go @@ -15,7 +15,7 @@ func TestDirectClient_parseHTMLKataDasar(t *testing.T) { t.Fatal(err) } - cl, err := newDirectClient(nil) + cl, err := newDirectClient() if err != nil { t.Fatal(err) } @@ -8,6 +8,7 @@ import ( "fmt" "log" stdhttp "net/http" + "os" "strings" "sync" "time" @@ -17,6 +18,8 @@ import ( ) const ( + envKbbiSandi = "KBBI_SANDI" + envKbbiSurel = "KBBI_SUREL" defRootDir = "_www-kbbi" defListen = ":3394" emptyResponse = "{}" @@ -68,7 +71,7 @@ func NewServer(kamusStorage string) (server *Server, err error) { return nil, fmt.Errorf("http.NewServer: %w", err) } - server.forwardc, err = newDirectClient(nil) + server.forwardc, err = newDirectClient() if err != nil { return nil, fmt.Errorf("http.NewServer: %w", err) } @@ -78,6 +81,17 @@ func NewServer(kamusStorage string) (server *Server, err error) { return nil, fmt.Errorf("http.NewServer: %w", err) } + if !server.forwardc.isAuthenticated() { + surel := os.Getenv(envKbbiSurel) + sandi := os.Getenv(envKbbiSandi) + if len(surel) > 0 && len(sandi) > 0 { + err = server.forwardc.login(surel, sandi) + if err != nil { + return nil, err + } + } + } + return server, nil } |
