From 29905485926706ca462c95a793d4312e86434357 Mon Sep 17 00:00:00 2001 From: Shulhan Date: Thu, 12 Dec 2024 19:56:01 +0700 Subject: client: use libhttp.Client instead of standard http.Client The libhttp.Client provides much simpler API, where we can define global headers once, like UserAgent and does not need to manually read the response body. --- client.go | 138 +++++++++++++++++++++++++++++--------------------------------- 1 file changed, 64 insertions(+), 74 deletions(-) diff --git a/client.go b/client.go index d9781ab..a54f179 100644 --- a/client.go +++ b/client.go @@ -8,7 +8,6 @@ import ( "encoding/gob" "errors" "fmt" - "io" "log" "net/http" "net/http/cookiejar" @@ -25,12 +24,14 @@ import ( ) const ( - kbbiUrlBase = "https://kbbi.kemdikbud.go.id" - kbbiUrlLogin = kbbiUrlBase + "/Account/Login" + kbbiHost = `kbbi.kemdikbud.go.id` + kbbiUrlBase = `https://` + kbbiHost + kbbiUrlLogin = `/Account/Login` kbbiPathEntri = "/entri/" attrNameClass = "class" attrNameHref = "href" + attrNameName = `name` attrNameTitle = "title" attrNameValue = "value" @@ -64,7 +65,7 @@ const ( // Client for official KBBI web using HTTP. type Client struct { - httpc *http.Client + httpc *libhttp.Client cookieURL *url.URL baseDir string cookies []*http.Cookie @@ -87,14 +88,18 @@ func NewClient() (cl *Client, err error) { return nil, fmt.Errorf("New: %w", err) } + var clientOpts = libhttp.ClientOptions{ + ServerURL: kbbiUrlBase, + Timeout: defTimeout, + } + cl = &Client{ cookieURL: cookieURL, - httpc: &http.Client{ - Jar: jar, - Timeout: defTimeout, - }, + httpc: libhttp.NewClient(clientOpts), } + cl.httpc.Jar = jar + err = cl.loadCookies() if err != nil { return nil, fmt.Errorf("New: %w", err) @@ -120,22 +125,17 @@ func (cl *Client) Lookup(ins []string) (res LookupResponse, err error) { kata := &Word{} res[in] = kata - entriURL := kbbiUrlBase + kbbiPathEntri + in - httpRes, err := cl.httpc.Get(entriURL) - if err != nil { - kata.err = err - continue + var req = libhttp.ClientRequest{ + Path: kbbiPathEntri + in, } - - defer httpRes.Body.Close() - - body, err := io.ReadAll(httpRes.Body) + var resp *libhttp.ClientResponse + resp, err = cl.httpc.Get(req) if err != nil { kata.err = err continue } - err = kata.parseHTMLEntri(in, body) + err = kata.parseHTMLEntri(in, resp.Body) if err != nil { kata.err = err } @@ -158,6 +158,7 @@ func (cl *Client) ListRootWords(pageStart, pageEnd int) (rootWords Words, err er } var ( + logp = `ListRootWords` params = url.Values{ paramNameMasukan: []string{paramValueDasar}, paramNameMasukanLengkap: []string{paramValueDasar}, @@ -167,39 +168,36 @@ func (cl *Client) ListRootWords(pageStart, pageEnd int) (rootWords Words, err er n int ) - urlPage := kbbiUrlBase + "/Cari/Jenis?" + urlPage := `/Cari/Jenis` rootWords = make(Words) for ; pageStart <= pageEnd; pageStart++ { params.Set(paramNamePage, strconv.Itoa(pageStart)) - req, err := http.NewRequest(http.MethodGet, urlPage+params.Encode(), nil) - if err != nil { - return rootWords, err + var req = libhttp.ClientRequest{ + Method: http.MethodGet, + Path: urlPage, + Params: params, + Type: libhttp.RequestTypeQuery, } - - res, err := cl.httpc.Do(req) - if err != nil { - return rootWords, fmt.Errorf("ListRootWords: page %d: %w", pageStart, err) - } - - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) + var resp *libhttp.ClientResponse + resp, err = cl.httpc.Get(req) if err != nil { - return rootWords, fmt.Errorf("ListRootWords: page %d: %w", pageStart, err) + err = fmt.Errorf(`%s: halaman %d: %w`, logp, pageStart, err) + return rootWords, err } - got, err = cl.parseHTMLRootWords(body) + got, err = cl.parseHTMLRootWords(resp.Body) if err != nil { - return rootWords, fmt.Errorf("ListRootWords: page %d: %w", pageStart, err) + err = fmt.Errorf(`%s: halaman %d: %w`, logp, pageStart, err) + return rootWords, err } if len(got) == 0 { break } - fmt.Printf("%d: got words: %v\n", pageStart, got) + log.Printf(`%s: halaman %d: daftar kata: %v`, logp, pageStart, got) n = rootWords.merge(got) if n == 0 { @@ -207,8 +205,8 @@ func (cl *Client) ListRootWords(pageStart, pageEnd int) (rootWords Words, err er break } - log.Printf("ListRootWords: halaman %d, jumlah kata %d, total kata %d", - pageStart, len(got), len(rootWords)) + log.Printf(`%s: halaman %d: jumlah kata %d, total kata %d`, + logp, pageStart, len(got), len(rootWords)) } return rootWords, nil @@ -222,9 +220,11 @@ func (cl *Client) IsAuthenticated() bool { // Login authenticate the client using user email and password. func (cl *Client) Login(email, pass string) (err error) { + var logp = `Login` + tokenLogin, err := cl.preLogin() if err != nil { - return fmt.Errorf("Login: %w", err) + return fmt.Errorf(`%s: %w`, logp, err) } params := url.Values{ @@ -234,29 +234,21 @@ func (cl *Client) Login(email, pass string) (err error) { paramNameIngatSaya: []string{paramValueFalse}, } - reqBody := strings.NewReader(params.Encode()) - - req, err := http.NewRequest(http.MethodPost, kbbiUrlLogin, reqBody) - if err != nil { - return fmt.Errorf("Login: %w", err) + var req = libhttp.ClientRequest{ + Method: http.MethodPost, + Path: kbbiUrlLogin, + Params: params, + Type: libhttp.RequestTypeForm, } - - req.Header.Set(libhttp.HeaderContentType, libhttp.ContentTypeForm) - - res, err := cl.httpc.Do(req) + var resp *libhttp.ClientResponse + resp, err = cl.httpc.PostForm(req) if err != nil { - return fmt.Errorf("Login: %w", err) + return fmt.Errorf(`%s: %w`, logp, err) } - defer res.Body.Close() - - resBody, err := io.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("Login: %w", err) - } - - if res.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("login: %d %s", res.StatusCode, resBody) + if resp.HTTPResponse.StatusCode >= http.StatusBadRequest { + return fmt.Errorf(`%s: %d %s`, logp, + resp.HTTPResponse.StatusCode, resp.Body) } cl.cookies = cl.httpc.Jar.Cookies(cl.cookieURL) @@ -316,35 +308,33 @@ func (cl *Client) parseHTMLLogin(htmlBody []byte) ( continue } + var name = node.GetAttrValue(attrNameName) + if name != paramNameRequestVerificationToken { + continue + } + token := node.GetAttrValue(attrNameValue) if len(token) > 0 { return token, nil } } - return "", fmt.Errorf("token login not found") + return ``, errors.New(`token login not found`) } // preLogin initialize the client to get the first cookie. func (cl *Client) preLogin() (token string, err error) { - req, err := http.NewRequest(http.MethodGet, kbbiUrlLogin, nil) - if err != nil { - return "", err + var req = libhttp.ClientRequest{ + Method: http.MethodGet, + Path: kbbiUrlLogin, } - - res, err := cl.httpc.Do(req) - if err != nil { - return "", err - } - - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) + var resp *libhttp.ClientResponse + resp, err = cl.httpc.Get(req) if err != nil { return "", err } - token, err = cl.parseHTMLLogin(body) + token, err = cl.parseHTMLLogin(resp.Body) if err != nil { return "", err } @@ -385,7 +375,7 @@ func (cl *Client) loadCookies() (err error) { func (cl *Client) saveCookies() { err := os.MkdirAll(filepath.Join(cl.baseDir, configDir), 0700) if err != nil { - log.Println("saveCookies:", err) + log.Println(`saveCookies:`, err) } f := filepath.Join(cl.baseDir, configDir, cookieFile) @@ -394,11 +384,11 @@ func (cl *Client) saveCookies() { enc := gob.NewEncoder(&buf) err = enc.Encode(cl.cookies) if err != nil { - log.Println("saveCookies: ", err) + log.Println(`saveCookies:`, err) } err = os.WriteFile(f, buf.Bytes(), 0600) if err != nil { - log.Println("saveCookies: ", err) + log.Println(`saveCookies:`, err) } } -- cgit v1.3