summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <m.shulhan@gmail.com>2020-04-12 04:35:58 +0700
committerShulhan <m.shulhan@gmail.com>2020-04-12 04:35:58 +0700
commit9cf57fc952e4161c8e7fc5c9de8869d3137acb20 (patch)
tree1fe91b6316ea34ff6600878f7d12b474da290361
parentc6f1ebdc315eda5f41fd7a3f30c693b243882be8 (diff)
downloadkamusku-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.go40
-rw-r--r--cmd/kbbi/main.go74
-rw-r--r--direct_client.go120
-rw-r--r--direct_client_test.go2
-rw-r--r--server.go16
5 files changed, 137 insertions, 115 deletions
diff --git a/client.go b/client.go
index c23100f..bf8c599 100644
--- a/client.go
+++ b/client.go
@@ -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)
}
diff --git a/server.go b/server.go
index f4401e8..aea5982 100644
--- a/server.go
+++ b/server.go
@@ -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
}