From 2f84db7441e8ee50d86ffd33071f1880a4ffe555 Mon Sep 17 00:00:00 2001 From: "Joice M. Joseph" Date: Thu, 16 Jul 2020 22:22:23 +0530 Subject: [PATCH 01/56] added go mod, golang ci --- go.mod | 11 +++++++ go.sum | 37 ++++++++++++++++++++++ http_handlers.go | 78 ++++++++++++++++++++++++++++++++-------------- main.go | 36 +++++++++++++-------- server.go | 4 +++ sync.go | 69 +++++++++++++++++++++++++++++++--------- varnam_handlers.go | 46 ++++++++++++++++++++------- varnam_learn.go | 3 ++ 8 files changed, 222 insertions(+), 62 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cd3e04f --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/varnamproject/varnamd + +go 1.14 + +require ( + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e + github.com/golang/protobuf v1.4.2 // indirect + github.com/gorilla/mux v1.7.4 + github.com/mattn/go-sqlite3 v1.14.0 + github.com/varnamproject/libvarnam-golang v0.0.0-20150608021401-b24ba4c3d932 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a9b9603 --- /dev/null +++ b/go.sum @@ -0,0 +1,37 @@ +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/varnamproject/libvarnam-golang v0.0.0-20150608021401-b24ba4c3d932 h1:Z6VhoopafnsFFYf8KwGty1dmJteTCM/mLGRAvc81QPk= +github.com/varnamproject/libvarnam-golang v0.0.0-20150608021401-b24ba4c3d932/go.mod h1:p0y0nS7FVUFgPK6dOkEY/NiOI7+aBeYgrl4ACVFuLIo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= diff --git a/http_handlers.go b/http_handlers.go index 19e448e..38f9bed 100644 --- a/http_handlers.go +++ b/http_handlers.go @@ -3,6 +3,7 @@ package main import ( "bytes" "compress/gzip" + "context" "encoding/json" "errors" "fmt" @@ -24,6 +25,7 @@ var errCacheSkipped = errors.New("cache skipped") // Data will be set if the cache returns CacheSkipped type varnamCacheContext struct { Data []byte + context.Context } type standardResponse struct { @@ -34,10 +36,12 @@ type standardResponse struct { func newStandardResponse(err string) standardResponse { s := standardResponse{Success: true, Error: "", At: time.Now().UTC().String()} + if err != "" { s.Error = err s.Success = false } + return s } @@ -67,6 +71,7 @@ type requestParams struct { func parseParams(r *http.Request) *requestParams { params := mux.Vars(r) downloadStart, _ := strconv.Atoi(params["downloadStart"]) + return &requestParams{langCode: params["langCode"], word: params["word"], downloadStart: downloadStart} } @@ -74,6 +79,7 @@ func parseParams(r *http.Request) *requestParams { func corsHandler(h http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") + if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) } else { @@ -94,49 +100,54 @@ func recoverHandler(next http.Handler) http.Handler { }() next.ServeHTTP(w, r) } + return http.HandlerFunc(fn) } func renderError(w http.ResponseWriter, err error) { w.Header().Set("Content-Type", "application/json; charset=utf-8") + if err != nil { w.WriteHeader(http.StatusBadRequest) + errorData := newStandardResponse(err.Error()) - json.NewEncoder(w).Encode(errorData) + _ = json.NewEncoder(w).Encode(errorData) } } func renderGzippedJSON(w http.ResponseWriter, data []byte) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Encoding", "gzip") - w.Write(data) + _, _ = w.Write(data) } func renderJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(data) + _ = json.NewEncoder(w).Encode(data) } func getLanguageAndWord(r *http.Request) (langCode string, word string) { params := mux.Vars(r) langCode = params["langCode"] word = params["word"] + return } -func getLangCode(r *http.Request) string { - params := mux.Vars(r) - return params["langCode"] -} +// func getLangCode(r *http.Request) string { +// params := mux.Vars(r) +// return params["langCode"] +// } -func getWord(r *http.Request) string { - params := mux.Vars(r) - return params["word"] -} +// func getWord(r *http.Request) string { +// params := mux.Vars(r) +// return params["word"] +// } func statusHandler(w http.ResponseWriter, r *http.Request) { - uptime := time.Now().Sub(startedAt) + uptime := time.Since(startedAt) + resp := struct { Version string `json:"version"` Uptime string `json:"uptime"` @@ -153,6 +164,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { func transliterationHandler(w http.ResponseWriter, r *http.Request) { langCode, word := getLanguageAndWord(r) words, err := transliterate(langCode, word) + if err != nil { renderError(w, err) } else { @@ -164,6 +176,7 @@ func transliterationHandler(w http.ResponseWriter, r *http.Request) { func reverseTransliterationHandler(w http.ResponseWriter, r *http.Request) { langCode, word := getLanguageAndWord(r) result, err := reveseTransliterate(langCode, word) + if err != nil { renderError(w, err) } else { @@ -180,13 +193,14 @@ func reverseTransliterationHandler(w http.ResponseWriter, r *http.Request) { func metadataHandler(w http.ResponseWriter, r *http.Request) { schemeIdentifier, _ := getLanguageAndWord(r) - getOrCreateHandler(schemeIdentifier, func(handle *libvarnam.Varnam) (data interface{}, err error) { + _, _ = getOrCreateHandler(schemeIdentifier, func(handle *libvarnam.Varnam) (data interface{}, err error) { details, err := handle.GetCorpusDetails() if err != nil { renderError(w, err) return } renderJSON(w, &metaResponse{Result: details, standardResponse: newStandardResponse("")}) + return }) } @@ -194,23 +208,25 @@ func metadataHandler(w http.ResponseWriter, r *http.Request) { func downloadHandler(w http.ResponseWriter, r *http.Request) { params := parseParams(r) if params.downloadStart < 0 { - renderError(w, errors.New("Invalid parameters")) + renderError(w, errors.New("invalid parameters")) return } - fillCache := func(ctx groupcache.Context, key string, dest groupcache.Sink) error { + fillCache := func(ctx context.Context, key string, dest groupcache.Sink) error { // cache miss, fetch from DB // key is in the form + parts := strings.Split(key, "+") - schemeId := parts[0] + schemeID := parts[0] downloadStart, _ := strconv.Atoi(parts[1]) - words, err := getWords(schemeId, downloadStart) + words, err := getWords(schemeID, downloadStart) + if err != nil { return err } response := downloadResponse{Count: len(words), Words: words, standardResponse: newStandardResponse("")} b, err := json.Marshal(response) + if err != nil { return err } @@ -218,17 +234,21 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) { // gzipping the response so that it can be served directly var gb bytes.Buffer gWriter := gzip.NewWriter(&gb) - defer gWriter.Close() - gWriter.Write(b) - gWriter.Flush() + + defer func() { _ = gWriter.Close() }() + + _, _ = gWriter.Write(b) + _ = gWriter.Flush() if len(words) < downloadPageSize { - varnamCtx := ctx.(*varnamCacheContext) + varnamCtx, _ := ctx.(*varnamCacheContext) varnamCtx.Data = gb.Bytes() + return errCacheSkipped } - dest.SetBytes(gb.Bytes()) + _ = dest.SetBytes(gb.Bytes()) + return nil } @@ -247,7 +267,9 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) { cacheGroup := cacheGroups[params.langCode] ctx := varnamCacheContext{} + var data []byte + if err := cacheGroup.Get(&ctx, fmt.Sprintf("%s+%d", params.langCode, params.downloadStart), groupcache.AllocatingByteSliceSink(&data)); err != nil { if err == errCacheSkipped { renderGzippedJSON(w, ctx.Data) @@ -255,6 +277,7 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) { } renderError(w, err) + return } @@ -266,25 +289,32 @@ func languagesHandler(w http.ResponseWriter, r *http.Request) { } func learnHandler(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) var args Args + + decoder := json.NewDecoder(r.Body) + if e := decoder.Decode(&args); e != nil { renderError(w, e) return } ch, ok := learnChannels[args.LangCode] + if !ok { - renderError(w, errors.New("Unable to find language")) + renderError(w, errors.New("unable to find language")) + return } + go func(word string) { ch <- word }(args.Word) + renderJSON(w, "success") } func toggleDownloadEnabledStatus(w http.ResponseWriter, r *http.Request, status bool) { params := parseParams(r) err := varnamdConfig.setDownloadStatus(params.langCode, status) + if err != nil { renderError(w, err) } else { diff --git a/main.go b/main.go index 9318a68..b75144b 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "errors" "flag" "fmt" "log" @@ -34,34 +33,38 @@ var ( // varnamd configurations // this is populated from various command line flags type config struct { - upstream string - schemesToDownload map[string]bool - syncIntervalInSecs time.Duration + upstream string + schemesToDownload map[string]bool + syncInterval time.Duration } func initConfig() *config { toDownload := make(map[string]bool) schemes := strings.Split(downloadEnabledSchemes, ",") + for _, scheme := range schemes { s := strings.TrimSpace(scheme) + if s != "" { if !isValidSchemeIdentifier(s) { panic(fmt.Sprintf("%s is not a valid libvarnam supported scheme", s)) } + toDownload[s] = true } } return &config{upstream: upstreamURL, schemesToDownload: toDownload, - syncIntervalInSecs: time.Duration(syncIntervalInSecs)} + syncInterval: time.Duration(syncIntervalInSecs)} } func (c *config) setDownloadStatus(langCode string, status bool) error { if !isValidSchemeIdentifier(langCode) { - return errors.New(fmt.Sprintf("%s is not a valid libvarnam supported scheme", langCode)) + return fmt.Errorf("%s is not a valid libvarnam supported scheme", langCode) } c.schemesToDownload[langCode] = status + if status { // when varnamd was started without any langcodes to sync, the dispatcher won't be running // in that case, we need to start the dispatcher since we have a new lang code to download now @@ -74,15 +77,16 @@ func (c *config) setDownloadStatus(langCode string, status bool) error { func getConfigDir() string { if runtime.GOOS == "windows" { return path.Join(os.Getenv("localappdata"), ".varnamd") - } else { - return path.Join(os.Getenv("HOME"), ".varnamd") } + + return path.Join(os.Getenv("HOME"), ".varnamd") } func getLogsDir() string { d := getConfigDir() logsDir := path.Join(d, "logs") - err := os.MkdirAll(logsDir, 0777) + err := os.MkdirAll(logsDir, 0750) + if err != nil { panic(err) } @@ -93,10 +97,12 @@ func getLogsDir() string { func redirectLogToFile() { year, month, day := time.Now().Date() logfile := path.Join(getLogsDir(), fmt.Sprintf("%d-%d-%d.log", year, month, day)) - f, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + f, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) + if err != nil { panic(err) } + log.SetOutput(f) } @@ -123,22 +129,26 @@ func syncRequired() bool { // Starts the sync process only if it is not running func startSyncDispatcher() { if syncRequired() && !syncDispatcherRunning { - sync := newSyncDispatcher(varnamdConfig.syncIntervalInSecs * time.Second) + sync := newSyncDispatcher(varnamdConfig.syncInterval * time.Second) sync.start() sync.runNow() // run one round of sync immediatly rather than waiting for the next interval to occur + syncDispatcherRunning = true } } func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - flag.Parse() varnamdConfig = initConfig() startedAt = time.Now() + + runtime.GOMAXPROCS(runtime.NumCPU()) + flag.Parse() + if version { fmt.Println(varnamdVersion) os.Exit(0) } + if logToFile { redirectLogToFile() } diff --git a/server.go b/server.go index 6156b6e..fe7c4f9 100644 --- a/server.go +++ b/server.go @@ -12,7 +12,9 @@ import ( func startDaemon() { initLanguageChannels() initLearnChannels() + r := mux.NewRouter() + r.HandleFunc("/tl/{langCode}/{word}", transliterationHandler).Methods("GET") r.HandleFunc("/rtl/{langCode}/{word}", reverseTransliterationHandler).Methods("GET") r.HandleFunc("/meta/{langCode}", metadataHandler).Methods("GET") @@ -20,6 +22,7 @@ func startDaemon() { r.HandleFunc("/learn", learnHandler).Methods("POST") r.HandleFunc("/languages", languagesHandler).Methods("GET") r.HandleFunc("/status", statusHandler).Methods("GET") + if enableInternalApis { r.HandleFunc("/sync/download/{langCode}/enable", enableDownload).Methods("POST") r.HandleFunc("/sync/download/{langCode}/disable", disableDownload).Methods("POST") @@ -29,6 +32,7 @@ func startDaemon() { address := fmt.Sprintf("%s:%d", host, port) log.Printf("Listening on %s", address) + if enableSSL { if err := http.ListenAndServeTLS(address, certFilePath, keyFilePath, recoverHandler(corsHandler(r))); err != nil { log.Fatalln(err) diff --git a/sync.go b/sync.go index 52f0b53..75729d5 100644 --- a/sync.go +++ b/sync.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "path" + "path/filepath" "strconv" "strings" "time" @@ -21,21 +22,25 @@ type syncDispatcher struct { ticker *time.Ticker } -func newSyncDispatcher(intervalInSeconds time.Duration) *syncDispatcher { - return &syncDispatcher{ticker: time.NewTicker(intervalInSeconds), force: make(chan bool), quit: make(chan struct{})} +func newSyncDispatcher(interval time.Duration) *syncDispatcher { + return &syncDispatcher{ticker: time.NewTicker(interval), force: make(chan bool), quit: make(chan struct{})} } func (s *syncDispatcher) start() { err := createSyncMetadataDir() + if err != nil { fmt.Printf("Failed to create sync metadata directory. Sync will be disabled.\nActual error: %s\n", err.Error()) return } + for s := range varnamdConfig.schemesToDownload { // download cache directory for each of the languages err = createLearnQueueDir(s) + if err != nil { - fmt.Printf("Failed to create learn queue directory for '%s'. Sync will be disabled.\nActual error: %s\n", err.Error()) + fmt.Printf("Failed to create learn queue directory for '%s'. Sync will be disabled.\nActual error: %s\n", s, err.Error()) + return } } @@ -55,9 +60,9 @@ func (s *syncDispatcher) start() { }() } -func (s *syncDispatcher) stop() { - close(s.quit) -} +// func (s *syncDispatcher) stop() { +// close(s.quit) +// } func (s *syncDispatcher) runNow() { s.force <- true @@ -106,12 +111,14 @@ func syncWordsFromUpstreamFor(langCode string) { func addFilesFromLocalLearnQueue(langCode string, files []string, filesToLearn chan string) { if files != nil { log.Printf("Adding %d files to learn from local learn queue\n", len(files)) + for _, f := range files { filesToLearn <- f } } else { log.Printf("Local learn queue for '%s' is empty", langCode) } + close(filesToLearn) } @@ -119,16 +126,22 @@ func downloadAllWords(langCode string, corpusSize int, output chan string) { for { offset := getDownloadOffset(langCode) log.Printf("Offset: %d\n", offset) + if offset >= corpusSize { break } + filePath, err := downloadWordsAndUpdateOffset(langCode, offset) + if err != nil { break } + output <- filePath } + log.Println("Local copy is upto date. No need to download from upstream") + close(output) } @@ -139,9 +152,11 @@ func learnAll(langCode string, filesToLearn chan string) { } func learnFromFile(langCode, fileToLearn string) { - log.Printf("Learning from %s\n", fileToLearn) start := time.Now() - getOrCreateHandler(langCode, func(handle *libvarnam.Varnam) (data interface{}, err error) { + + log.Printf("Learning from %s\n", fileToLearn) + + _, _ = getOrCreateHandler(langCode, func(handle *libvarnam.Varnam) (data interface{}, err error) { learnStatus, err := handle.LearnFromFile(fileToLearn) end := time.Now() if err != nil { @@ -178,25 +193,33 @@ func downloadWordsAndUpdateOffset(langCode string, offset int) (string, error) { func getCorpusDetails(langCode string) (*libvarnam.CorpusDetails, error) { url := fmt.Sprintf("%s/meta/%s", varnamdConfig.upstream, langCode) log.Printf("Fetching corpus details for '%s'\n", langCode) + var m metaResponse err := getJSONResponse(url, &m) + if err != nil { return nil, err } + log.Printf("Corpus size: %d\n", m.Result.WordsCount) + return m.Result, nil } // Downloads words from upstream starting from the specified offset and stores it locally in the learn queue // Returns the number of words downloaded, local file path and error if any func downloadWords(langCode string, offset int) (totalWordsDownloaded int, downloadedFilePath string, err error) { - url := fmt.Sprintf("%s/download/%s/%d", varnamdConfig.upstream, langCode, offset) var response downloadResponse + + url := fmt.Sprintf("%s/download/%s/%d", varnamdConfig.upstream, langCode, offset) err = getJSONResponse(url, &response) + if err != nil { return 0, "", err } + downloadedFilePath, err = transformAndPersistWords(langCode, offset, &response) + if err != nil { log.Printf("Download was successful, but failed to persist to local learn queue. %s\n", err.Error()) return 0, "", err @@ -208,10 +231,12 @@ func downloadWords(langCode string, offset int) (totalWordsDownloaded int, downl func transformAndPersistWords(langCode string, offset int, dresp *downloadResponse) (string, error) { learnQueueDir := getLearnQueueDir(langCode) targetFile, err := os.Create(path.Join(learnQueueDir, fmt.Sprintf("%s.%d", langCode, offset))) + if err != nil { return "", err } - defer targetFile.Close() + + defer func() { _ = targetFile.Close() }() for _, word := range dresp.Words { _, err = targetFile.WriteString(fmt.Sprintf("%s %d\n", word.Word, word.Confidence)) @@ -219,13 +244,16 @@ func transformAndPersistWords(langCode string, offset int, dresp *downloadRespon return "", err } } + return targetFile.Name(), nil } func getFilesFromLearnQueue(langCode string) []string { var files []string + learnQueueDir := getLearnQueueDir(langCode) queueContents, err := ioutil.ReadDir(learnQueueDir) + if err != nil { return nil } @@ -241,22 +269,29 @@ func getFilesFromLearnQueue(langCode string) []string { func getJSONResponse(url string, output interface{}) error { log.Printf("GET: '%s'\n", url) + resp, err := http.Get(url) + if err != nil { return err } - defer resp.Body.Close() + + defer func() { _ = resp.Body.Close() }() + jsonDecoder := json.NewDecoder(resp.Body) err = jsonDecoder.Decode(output) + if err != nil { return err } + return nil } func getDownloadOffset(langCode string) int { filePath := getDownloadOffsetMetadataFile(langCode) - content, err := ioutil.ReadFile(filePath) + content, err := ioutil.ReadFile(filepath.Clean(filePath)) + if err != nil { return 0 } @@ -281,30 +316,36 @@ func getDownloadOffsetMetadataFile(langCode string) string { func createLearnQueueDir(langCode string) error { queueDir := getLearnQueueDir(langCode) - err := os.MkdirAll(queueDir, 0777) + err := os.MkdirAll(queueDir, 0750) + if err != nil { return err } + return nil } func getLearnQueueDir(langCode string) string { syncDir := getSyncMetadataDir() queueDir := path.Join(syncDir, fmt.Sprintf("%s.learn.queue", langCode)) + return queueDir } func createSyncMetadataDir() error { syncDir := getSyncMetadataDir() - err := os.MkdirAll(syncDir, 0777) + err := os.MkdirAll(syncDir, 0750) + if err != nil { return err } + return nil } func getSyncMetadataDir() string { configDir := getConfigDir() syncDir := path.Join(configDir, "sync") + return syncDir } diff --git a/varnam_handlers.go b/varnam_handlers.go index 3bdd173..f6a7188 100644 --- a/varnam_handlers.go +++ b/varnam_handlers.go @@ -13,7 +13,7 @@ import ( ) type word struct { - Id int `json:"id"` + ID int `json:"id"` Confidence int `json:"confidence"` Word string `json:"word"` } @@ -24,8 +24,8 @@ var ( mutex *sync.Mutex once sync.Once schemeDetails = libvarnam.GetAllSchemeDetails() - peers = groupcache.NewHTTPPool("http://localhost") cacheGroups = make(map[string]*groupcache.Group) + // peers = groupcache.NewHTTPPool("http://localhost") ) func isValidSchemeIdentifier(id string) bool { @@ -34,6 +34,7 @@ func isValidSchemeIdentifier(id string) bool { return true } } + return false } @@ -41,9 +42,11 @@ func initLanguageChannels() { languageChannels = make(map[string]chan *libvarnam.Varnam) channelsCount = make(map[string]int) mutex = &sync.Mutex{} + for _, scheme := range schemeDetails { languageChannels[scheme.Identifier] = make(chan *libvarnam.Varnam, maxHandleCount) channelsCount[scheme.Identifier] = maxHandleCount + for i := 0; i < maxHandleCount; i++ { handle, err := libvarnam.Init(scheme.Identifier) if err != nil { @@ -57,22 +60,27 @@ func initLanguageChannels() { func getOrCreateHandler(schemeIdentifier string, f func(handle *libvarnam.Varnam) (data interface{}, err error)) (data interface{}, err error) { ch, ok := languageChannels[schemeIdentifier] if !ok { - return nil, errors.New("Invalid scheme identifier") + return nil, errors.New("invalid scheme identifier") } select { case handle := <-ch: data, err = f(handle) + go func() { ch <- handle }() case <-time.After(800 * time.Millisecond): var handle *libvarnam.Varnam handle, err = libvarnam.Init(schemeIdentifier) + if err != nil { log.Println(err) - return nil, errors.New("Unable to initialize varnam handle") + return nil, errors.New("unable to initialize varnam handle") } + data, err = f(handle) + go sendHandlerToChannel(schemeIdentifier, handle, ch) } + return } @@ -89,10 +97,12 @@ func getWords(schemeIdentifier string, downloadStart int) ([]*word, error) { }) db, err := sql.Open("sqlite3", filepath.(string)) + if err != nil { return nil, err } - defer db.Close() + + defer func() { _ = db.Close() }() // Making an index for all learned words so that download is faster // this needs to be removed when there is more clarity on how learned words needs to be handled @@ -101,19 +111,30 @@ func getWords(schemeIdentifier string, downloadStart int) ([]*word, error) { return nil, err } - q := "select id, word, confidence from words where id in (select distinct(word_id) from patterns_content where learned = 1) order by id asc limit ? offset ?;" + q := `select id, word, confidence from words where id in (select distinct(word_id) from patterns_content where learned = 1) order by id asc limit ? offset ?;` rows, err := db.Query(q, downloadPageSize, downloadStart) + if err != nil { return nil, err } - defer rows.Close() + + if rows.Err() != nil { + return nil, rows.Err() + } + + defer func() { _ = rows.Close() }() var words []*word + for rows.Next() { - var id, confidence int - var _word string - rows.Scan(&id, &_word, &confidence) - words = append(words, &word{Id: id, Confidence: confidence, Word: _word}) + var ( + id, confidence int + _word string + ) + + _ = rows.Scan(&id, &_word, &confidence) + + words = append(words, &word{ID: id, Confidence: confidence, Word: _word}) } return words, nil @@ -130,11 +151,14 @@ func sendHandlerToChannel(schemeIdentifier string, handle *libvarnam.Varnam, ch mutex.Lock() count := channelsCount[schemeIdentifier] mutex.Unlock() + if count == maxHandleCount { log.Printf("Throw away handle") handle.Destroy() + return } + select { case ch <- handle: mutex.Lock() diff --git a/varnam_learn.go b/varnam_learn.go index 6d23eb6..bb8143d 100644 --- a/varnam_learn.go +++ b/varnam_learn.go @@ -6,6 +6,7 @@ import ( "github.com/varnamproject/libvarnam-golang" ) +// Args to read. type Args struct { LangCode string `json:"lang"` Word string `json:"word"` @@ -20,9 +21,11 @@ func initLearnChannels() { for _, scheme := range schemeDetails { learnChannels[scheme.Identifier] = make(chan string, 100) handle, err := libvarnam.Init(scheme.Identifier) + if err != nil { log.Fatal("Unable to initialize varnam for lang", scheme.LangCode) } + go listenForWords(scheme.Identifier, handle) } } From b55980f6c0d8fcbe203fc6a6246e3fc73cc0ecad Mon Sep 17 00:00:00 2001 From: "Joice M. Joseph" Date: Thu, 16 Jul 2020 23:39:07 +0530 Subject: [PATCH 02/56] migrated to echo --- go.mod | 8 +- go.sum | 58 ++++++++++++- http_handlers.go | 222 ++++++++++++++++++++++------------------------- server.go | 47 +++++----- 4 files changed, 185 insertions(+), 150 deletions(-) diff --git a/go.mod b/go.mod index cd3e04f..a7fbcdd 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,13 @@ go 1.14 require ( github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/protobuf v1.4.2 // indirect - github.com/gorilla/mux v1.7.4 + github.com/labstack/echo/v4 v4.1.16 + github.com/mattn/go-colorable v0.1.7 // indirect github.com/mattn/go-sqlite3 v1.14.0 + github.com/valyala/fasttemplate v1.2.0 // indirect github.com/varnamproject/libvarnam-golang v0.0.0-20150608021401-b24ba4c3d932 + golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 // indirect + golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect + golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect + golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index a9b9603..a234f57 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -13,19 +17,65 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o= +github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.0 h1:y3yXRCoDvC2HTtIHvL2cc7Zd+bqA+zqDO6oQzsJO07E= +github.com/valyala/fasttemplate v1.2.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/varnamproject/libvarnam-golang v0.0.0-20150608021401-b24ba4c3d932 h1:Z6VhoopafnsFFYf8KwGty1dmJteTCM/mLGRAvc81QPk= github.com/varnamproject/libvarnam-golang v0.0.0-20150608021401-b24ba4c3d932/go.mod h1:p0y0nS7FVUFgPK6dOkEY/NiOI7+aBeYgrl4ACVFuLIo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -35,3 +85,7 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/http_handlers.go b/http_handlers.go index 38f9bed..0c26ba0 100644 --- a/http_handlers.go +++ b/http_handlers.go @@ -15,7 +15,7 @@ import ( "time" "github.com/golang/groupcache" - "github.com/gorilla/mux" + "github.com/labstack/echo/v4" "github.com/varnamproject/libvarnam-golang" ) @@ -34,15 +34,8 @@ type standardResponse struct { At string `json:"at"` } -func newStandardResponse(err string) standardResponse { - s := standardResponse{Success: true, Error: "", At: time.Now().UTC().String()} - - if err != "" { - s.Error = err - s.Success = false - } - - return s +func newStandardResponse() standardResponse { + return standardResponse{Success: true, At: time.Now().UTC().String()} } type transliterationResponse struct { @@ -62,20 +55,6 @@ type downloadResponse struct { standardResponse } -type requestParams struct { - langCode string - word string - downloadStart int -} - -func parseParams(r *http.Request) *requestParams { - params := mux.Vars(r) - downloadStart, _ := strconv.Atoi(params["downloadStart"]) - - return &requestParams{langCode: params["langCode"], word: params["word"], - downloadStart: downloadStart} -} - func corsHandler(h http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") @@ -104,37 +83,6 @@ func recoverHandler(next http.Handler) http.Handler { return http.HandlerFunc(fn) } -func renderError(w http.ResponseWriter, err error) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - if err != nil { - w.WriteHeader(http.StatusBadRequest) - - errorData := newStandardResponse(err.Error()) - _ = json.NewEncoder(w).Encode(errorData) - } -} - -func renderGzippedJSON(w http.ResponseWriter, data []byte) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Header().Set("Content-Encoding", "gzip") - _, _ = w.Write(data) -} - -func renderJSON(w http.ResponseWriter, data interface{}) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(data) -} - -func getLanguageAndWord(r *http.Request) (langCode string, word string) { - params := mux.Vars(r) - langCode = params["langCode"] - word = params["word"] - - return -} - // func getLangCode(r *http.Request) string { // params := mux.Vars(r) // return params["langCode"] @@ -145,7 +93,7 @@ func getLanguageAndWord(r *http.Request) (langCode string, word string) { // return params["word"] // } -func statusHandler(w http.ResponseWriter, r *http.Request) { +func handleStatus(c echo.Context) error { uptime := time.Since(startedAt) resp := struct { @@ -155,61 +103,80 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { }{ varnamdVersion, uptime.String(), - newStandardResponse(""), + newStandardResponse(), } - renderJSON(w, resp) + return c.JSON(http.StatusOK, resp) } -func transliterationHandler(w http.ResponseWriter, r *http.Request) { - langCode, word := getLanguageAndWord(r) +func handleTransliteration(c echo.Context) error { + var ( + langCode = c.Param("langCode") + word = c.Param("word") + ) + + // langCode, word := getLanguageAndWord(r) words, err := transliterate(langCode, word) if err != nil { - renderError(w, err) - } else { - renderJSON(w, - transliterationResponse{standardResponse: newStandardResponse(""), Result: words.([]string), Input: word}) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error transliterating given string. message: %s", err.Error())) } + + return c.JSON(http.StatusOK, transliterationResponse{standardResponse: newStandardResponse(), Result: words.([]string), Input: word}) } -func reverseTransliterationHandler(w http.ResponseWriter, r *http.Request) { - langCode, word := getLanguageAndWord(r) +func handleReverseTransliteration(c echo.Context) error { + var ( + langCode = c.Param("langCode") + word = c.Param("word") + ) + result, err := reveseTransliterate(langCode, word) if err != nil { - renderError(w, err) - } else { - response := struct { - standardResponse - Result string `json:"result"` - }{ - newStandardResponse(""), - result.(string), - } - renderJSON(w, response) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error transliterating given string. message: %s", err.Error())) } + + response := struct { + standardResponse + Result string `json:"result"` + }{ + newStandardResponse(), + result.(string), + } + + return c.JSON(http.StatusOK, response) } -func metadataHandler(w http.ResponseWriter, r *http.Request) { - schemeIdentifier, _ := getLanguageAndWord(r) - _, _ = getOrCreateHandler(schemeIdentifier, func(handle *libvarnam.Varnam) (data interface{}, err error) { +func handleMetadata(c echo.Context) error { + var schemeIdentifier = c.Param("langCode") + + data, err := getOrCreateHandler(schemeIdentifier, func(handle *libvarnam.Varnam) (data interface{}, err error) { details, err := handle.GetCorpusDetails() + if err != nil { - renderError(w, err) - return + return nil, err } - renderJSON(w, &metaResponse{Result: details, standardResponse: newStandardResponse("")}) - return + return &metaResponse{Result: details, standardResponse: newStandardResponse()}, nil }) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error getting metadata. message: %s", err.Error())) + } + + return c.JSON(http.StatusOK, data) } -func downloadHandler(w http.ResponseWriter, r *http.Request) { - params := parseParams(r) - if params.downloadStart < 0 { - renderError(w, errors.New("invalid parameters")) - return +func handleDownload(c echo.Context) error { + var ( + langCode = c.Param("langCode") + // word = c.Param("word") + ) + + start, _ := strconv.Atoi(c.Param("downloadStart")) + + if start < 0 { + return echo.NewHTTPError(http.StatusBadRequest, "invalid parameter") } fillCache := func(ctx context.Context, key string, dest groupcache.Sink) error { @@ -224,7 +191,7 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) { return err } - response := downloadResponse{Count: len(words), Words: words, standardResponse: newStandardResponse("")} + response := downloadResponse{Count: len(words), Words: words, standardResponse: newStandardResponse()} b, err := json.Marshal(response) if err != nil { @@ -265,67 +232,82 @@ func downloadHandler(w http.ResponseWriter, r *http.Request) { } }) - cacheGroup := cacheGroups[params.langCode] + cacheGroup := cacheGroups[langCode] ctx := varnamCacheContext{} var data []byte - if err := cacheGroup.Get(&ctx, fmt.Sprintf("%s+%d", params.langCode, params.downloadStart), groupcache.AllocatingByteSliceSink(&data)); err != nil { + if err := cacheGroup.Get(&ctx, fmt.Sprintf("%s+%d", langCode, start), groupcache.AllocatingByteSliceSink(&data)); err != nil { if err == errCacheSkipped { - renderGzippedJSON(w, ctx.Data) - return - } + c.Response().Header().Set("Content-Type", "application/json; charset=utf-8") + c.Response().Header().Set("Content-Encoding", "gzip") - renderError(w, err) + return c.JSON(http.StatusOK, ctx.Data) + } - return + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error getting metadata. message: %s", err.Error())) } - renderGzippedJSON(w, data) + c.Response().Header().Set("Content-Type", "application/json; charset=utf-8") + c.Response().Header().Set("Content-Encoding", "gzip") + + return c.JSON(http.StatusOK, data) } -func languagesHandler(w http.ResponseWriter, r *http.Request) { - renderJSON(w, schemeDetails) +func handleLanguages(c echo.Context) error { + return c.JSON(http.StatusOK, schemeDetails) } -func learnHandler(w http.ResponseWriter, r *http.Request) { +func handlLearn(c echo.Context) error { var args Args - decoder := json.NewDecoder(r.Body) - - if e := decoder.Decode(&args); e != nil { - renderError(w, e) - return + if err := c.Bind(&args); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error getting metadata. message: %s", err.Error())) } ch, ok := learnChannels[args.LangCode] if !ok { - renderError(w, errors.New("unable to find language")) - - return + return echo.NewHTTPError(http.StatusBadRequest, "unable to find language") } go func(word string) { ch <- word }(args.Word) - renderJSON(w, "success") + return c.JSON(http.StatusOK, "success") } -func toggleDownloadEnabledStatus(w http.ResponseWriter, r *http.Request, status bool) { - params := parseParams(r) - err := varnamdConfig.setDownloadStatus(params.langCode, status) +func toggleDownloadEnabledStatus(langCode string, status bool) (interface{}, error) { + if err := varnamdConfig.setDownloadStatus(langCode, status); err != nil { + return nil, err + } + + return newStandardResponse(), nil +} + +func handleEnableDownload(c echo.Context) error { + var ( + langCode = c.Param("langCode") + ) + + data, err := toggleDownloadEnabledStatus(langCode, true) if err != nil { - renderError(w, err) - } else { - renderJSON(w, newStandardResponse("")) + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error getting metadata. message: %s", err.Error())) } -} -func enableDownload(w http.ResponseWriter, r *http.Request) { - toggleDownloadEnabledStatus(w, r, true) + return c.JSON(http.StatusOK, data) } -func disableDownload(w http.ResponseWriter, r *http.Request) { - toggleDownloadEnabledStatus(w, r, false) +func handleDisableDownload(c echo.Context) error { + var ( + langCode = c.Param("langCode") + ) + + data, err := toggleDownloadEnabledStatus(langCode, false) + + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("error getting metadata. message: %s", err.Error())) + } + + return c.JSON(http.StatusOK, data) } diff --git a/server.go b/server.go index fe7c4f9..d595f70 100644 --- a/server.go +++ b/server.go @@ -5,53 +5,46 @@ import ( "log" "net/http" "os" + "path/filepath" - "github.com/gorilla/mux" + "github.com/labstack/echo/v4" ) func startDaemon() { initLanguageChannels() initLearnChannels() - r := mux.NewRouter() + e := echo.New() - r.HandleFunc("/tl/{langCode}/{word}", transliterationHandler).Methods("GET") - r.HandleFunc("/rtl/{langCode}/{word}", reverseTransliterationHandler).Methods("GET") - r.HandleFunc("/meta/{langCode}", metadataHandler).Methods("GET") - r.HandleFunc("/download/{langCode}/{downloadStart}", downloadHandler).Methods("GET") - r.HandleFunc("/learn", learnHandler).Methods("POST") - r.HandleFunc("/languages", languagesHandler).Methods("GET") - r.HandleFunc("/status", statusHandler).Methods("GET") + e.GET("/tl/{langCode}/{word}", handleTransliteration) + e.GET("/rtl/{langCode}/{word}", handleReverseTransliteration) + e.GET("/meta/{langCode}", handleMetadata) + e.GET("/download/{langCode}/{downloadStart}", handleDownload) + e.POST("/learn", handlLearn) + e.GET("/languages", handleLanguages) + e.GET("/status", handleStatus) - if enableInternalApis { - r.HandleFunc("/sync/download/{langCode}/enable", enableDownload).Methods("POST") - r.HandleFunc("/sync/download/{langCode}/disable", disableDownload).Methods("POST") + if _, err := os.Stat(uiDir); err != nil { + log.Fatal("UI path doesnot exist", err) } - addUI(r) + e.Static("/", filepath.Clean(uiDir)) + + if enableInternalApis { + e.POST("/sync/download/{langCode}/enable", handleEnableDownload) + e.POST("/sync/download/{langCode}/disable", handleDisableDownload) + } address := fmt.Sprintf("%s:%d", host, port) log.Printf("Listening on %s", address) if enableSSL { - if err := http.ListenAndServeTLS(address, certFilePath, keyFilePath, recoverHandler(corsHandler(r))); err != nil { + if err := http.ListenAndServeTLS(address, certFilePath, keyFilePath, recoverHandler(corsHandler(e))); err != nil { log.Fatalln(err) } } else { - if err := http.ListenAndServe(address, recoverHandler(corsHandler(r))); err != nil { + if err := http.ListenAndServe(address, recoverHandler(corsHandler(e))); err != nil { log.Fatalln(err) } } } - -func addUI(r *mux.Router) { - if uiDir == "" { - return - } - - if _, err := os.Stat(uiDir); err != nil { - log.Fatalln("UI path doesnot exist", err) - } - - r.PathPrefix("/").Handler(http.FileServer(http.Dir(uiDir))) -} From 070c3f9a848c0d6f395b2fe648748d984ee7456c Mon Sep 17 00:00:00 2001 From: "Joice M. Joseph" Date: Fri, 17 Jul 2020 10:33:06 +0530 Subject: [PATCH 03/56] fix: issues in echo server config, html, css files formatted --- main.go | 4 +- server.go | 28 ++- ui/index.html | 215 ++++++------------- ui/javascripts/copycss.js | 18 +- ui/stylesheets/addon.css | 26 +-- ui/stylesheets/ambiance.css | 121 ++++++++--- ui/stylesheets/bootstrap-responsive.css | 49 +++-- ui/stylesheets/codemirror.css | 164 ++++++++++---- ui/stylesheets/print.css | 15 +- ui/stylesheets/styles.css | 273 +++++++++++++++++------- 10 files changed, 543 insertions(+), 370 deletions(-) diff --git a/main.go b/main.go index b75144b..b61f612 100644 --- a/main.go +++ b/main.go @@ -109,8 +109,8 @@ func redirectLogToFile() { func init() { flag.IntVar(&port, "p", 8080, "Run daemon in specified port") flag.IntVar(&maxHandleCount, "max-handle-count", 10, "Maximum number of handles can be opened for each language") - flag.StringVar(&host, "host", "", "Host for the varnam daemon server") - flag.StringVar(&uiDir, "ui", "", "UI directory path") + flag.StringVar(&host, "host", "localhost", "Host for the varnam daemon server") + flag.StringVar(&uiDir, "ui", "ui", "UI directory path") flag.BoolVar(&enableInternalApis, "enable-internal-apis", false, "Enable internal APIs") flag.BoolVar(&enableSSL, "enable-ssl", false, "Enables SSL") flag.StringVar(&certFilePath, "cert-file-path", "", "Certificate file path") diff --git a/server.go b/server.go index d595f70..77bc8e9 100644 --- a/server.go +++ b/server.go @@ -8,6 +8,7 @@ import ( "path/filepath" "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" ) func startDaemon() { @@ -24,7 +25,7 @@ func startDaemon() { e.GET("/languages", handleLanguages) e.GET("/status", handleStatus) - if _, err := os.Stat(uiDir); err != nil { + if _, err := os.Stat(filepath.Clean(uiDir)); err != nil { log.Fatal("UI path doesnot exist", err) } @@ -38,13 +39,30 @@ func startDaemon() { address := fmt.Sprintf("%s:%d", host, port) log.Printf("Listening on %s", address) + e.Use(middleware.Recover()) + e.Use(middleware.Logger()) + + e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodOptions}, + })) + + e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ + XSSProtection: "", + ContentTypeNosniff: "", + XFrameOptions: "", + HSTSMaxAge: 3600, + // ContentSecurityPolicy: "default-src 'self'", + })) + if enableSSL { - if err := http.ListenAndServeTLS(address, certFilePath, keyFilePath, recoverHandler(corsHandler(e))); err != nil { - log.Fatalln(err) + if err := e.StartTLS(address, certFilePath, keyFilePath); err != nil { + log.Fatal(err) } } else { - if err := http.ListenAndServe(address, recoverHandler(corsHandler(e))); err != nil { - log.Fatalln(err) + if err := e.Start(address); err != nil { + log.Fatal(err) } } + } diff --git a/ui/index.html b/ui/index.html index e4be1b9..26235f1 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,163 +1,76 @@ - - - - - - - - - - - - - Varnam - Type in Indian languages - - - + + + + + + - -
- Your Internet connection might have interrupted - -
-
-