├── Dockerfile ├── README.md ├── backend ├── main.go └── rpc.go ├── docker-compose.yml ├── frontend ├── README.md ├── app │ ├── assets │ │ ├── css │ │ │ └── index.css │ │ ├── favicon.png │ │ ├── js │ │ │ └── index.js │ │ └── page-image.png │ └── views │ │ └── index.html └── main.go ├── nginx └── ocrimage.conf ├── ocrservice-ui.jpg ├── rpc └── types.go ├── tests ├── runtest.sh ├── test_chi_sim.jpg ├── test_chi_tra.jpg └── test_eng.jpg └── worker └── main.go /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.9 2 | 3 | MAINTAINER onestraw 4 | 5 | RUN apt-get -qq update 6 | RUN apt-get install -y libleptonica-dev libtesseract-dev tesseract-ocr 7 | 8 | # Load languages 9 | RUN apt-get install -y \ 10 | tesseract-ocr-eng \ 11 | tesseract-ocr-chi-sim \ 12 | tesseract-ocr-chi-tra 13 | 14 | ADD . $GOPATH/src/github.com/onestraw/ocrservice 15 | WORKDIR $GOPATH/src/github.com/onestraw/ocrservice 16 | #RUN go get ./... 17 | RUN go get github.com/otiai10/gosseract 18 | RUN go get github.com/gin-gonic/gin 19 | RUN go get github.com/streadway/amqp 20 | RUN go get github.com/sirupsen/logrus 21 | 22 | RUN go install github.com/onestraw/ocrservice/worker 23 | RUN go install github.com/onestraw/ocrservice/backend 24 | RUN go install github.com/onestraw/ocrservice/frontend 25 | 26 | RUN ln -s $GOPATH/src/github.com/onestraw/ocrservice/frontend/app/ $GOPATH/src/github.com/onestraw/ocrservice/app 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCR Service 2 | 3 | 使用开源项目搭了一个`OCR`服务的完整技术栈 4 | 5 | - frontend 应用: UI完整拷贝自otiai10/ocrserver 6 | - 负载均衡器: Nginx 7 | - backend 业务层: Gin 8 | - worker 计算层: Tesseract 9 | - MQ 消息队列: RabbitMQ 10 | 11 | ## Architecture 12 | 13 | - - - - - - - - 14 | | APP | | APP | 15 | - - - - - - - - 16 | ^ ^ 17 | | | 18 | | | 19 | v v 20 | - - - - - - - - - - 21 | | Loadbalancer | 22 | - - - - - - - - - - 23 | ^ ^ ^ 24 | / | \ 25 | / | \ 26 | / | \ 27 | v v v 28 | - - - - - - - - - - - - - - - - - - 29 | | Backend | | Backend | | Backend | 30 | - - - - - - - - - - - - - - - - - - 31 | ^ | ^ ^ 32 | | | | | 33 | v v | v 34 | - - - - - - - - - - - - - - - - - - - - - - - - 35 | | | | | | | 36 | | RabbitMQ |q| |q| RPC | 37 | | | | | | | 38 | - - - - - - - - - - - - - - - - - - - - - - - - 39 | ^ | ^ ^ ^ 40 | | | | | \ 41 | v v | v \ 42 | - - - - - - - - - - - - - - - - - - - - - - - - 43 | | Worker | | Worker | | Worker | | Worker | 44 | - - - - - - - - - - - - - - - - - - - - - - - - 45 | 46 | ## Play with docker 47 | 48 | ### build image 49 | 50 | hub clone onestraw/ocrservice 51 | cd ocrservice 52 | docker build -t onestraw/ocrservice . 53 | 54 | ### Play 55 | 56 | docker-compose up -d 57 | # view in browser: 127.0.0.1:10001 58 | cd tests/ && ./runtest.sh 59 | # ... 60 | docker-compose logs 61 | docker-compose down 62 | 63 | ![ocrservice-ui](./ocrservice-ui.jpg) 64 | 65 | ## Todo 66 | 67 | - 支持PDF 68 | - 添加用户认证 69 | - 持久化存储 70 | - 高可用 71 | 72 | ## Reference 73 | 74 | - [Tesseract Open Source OCR Engine](https://github.com/tesseract-ocr/tesseract) 75 | - [Simple OCR server](https://github.com/otiai10/ocrserver) 76 | - [RabbitMQ RPC](http://www.rabbitmq.com/tutorials/tutorial-six-go.html) 77 | -------------------------------------------------------------------------------- /backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | log "github.com/sirupsen/logrus" 11 | 12 | "github.com/onestraw/ocrservice/rpc" 13 | ) 14 | 15 | const ( 16 | MAX_FILE_SIZE = 4 * 1024 * 1024 17 | ) 18 | 19 | var ( 20 | flagAddr = flag.String("addr", ":10002", "listening address") 21 | flagRabbitMQ = flag.String("rabbitmq", "amqp://guest:guest@localhost:5672/", "RabbitMQ Address") 22 | flagQueueName = flag.String("queue_name", "ocrimage", "Queue name for OCR image") 23 | ) 24 | 25 | func main() { 26 | flag.Parse() 27 | gin.DisableConsoleColor() 28 | 29 | router := gin.Default() 30 | router.POST("/ocrimage", OCRImage) 31 | router.Run(*flagAddr) 32 | } 33 | 34 | func OCRImage(ctx *gin.Context) { 35 | image, err := ctx.FormFile("file") 36 | if err != nil { 37 | log.Errorf("Fetch image error: %v", err) 38 | ctx.String(http.StatusBadRequest, err.Error()) 39 | return 40 | } 41 | log.Infof("Image: %s, size: %d", image.Filename, image.Size) 42 | if image.Size > MAX_FILE_SIZE { 43 | msg := fmt.Sprintf("Image size (%d) bytes exceed %d bytes", image.Size, MAX_FILE_SIZE) 44 | log.Errorf(msg) 45 | ctx.JSON(http.StatusBadRequest, msg) 46 | return 47 | } 48 | 49 | buf := make([]byte, image.Size) 50 | fd, err := image.Open() 51 | if err != nil { 52 | log.Errorf("Open image error: %v", err) 53 | ctx.JSON(http.StatusInternalServerError, err) 54 | return 55 | } 56 | defer fd.Close() 57 | count, err := fd.Read(buf) 58 | if err != nil { 59 | log.Errorf("Read image error: %v", err) 60 | ctx.JSON(http.StatusInternalServerError, err) 61 | return 62 | } 63 | log.Infof("Read %d bytes from %s", count, image.Filename) 64 | 65 | req := rpc.OCRImageRequest{ 66 | Lang: ctx.PostForm("languages"), 67 | Whitelist: ctx.PostForm("whitelist"), 68 | Image: buf, 69 | } 70 | log.Infof("RPC: send task to %s/%s", *flagRabbitMQ, *flagQueueName) 71 | resp, err := OCR_RPC(*flagRabbitMQ, *flagQueueName, req) 72 | if err != nil { 73 | log.Errorf("OCR image rpc error: %v", err) 74 | ctx.JSON(http.StatusInternalServerError, err) 75 | return 76 | } 77 | 78 | ctx.JSON(http.StatusOK, map[string]interface{}{ 79 | "result": strings.Trim(resp.Text, ctx.PostForm("trim")), 80 | "version": resp.Version, 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /backend/rpc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | 7 | "github.com/streadway/amqp" 8 | 9 | "github.com/onestraw/ocrservice/rpc" 10 | ) 11 | 12 | func failOnError(err error, msg string) { 13 | if err != nil { 14 | log.Fatalf("%s: %s", msg, err) 15 | } 16 | } 17 | 18 | func randInt(min int, max int) int { 19 | return min + rand.Intn(max-min) 20 | } 21 | 22 | func randomString(l int) string { 23 | bytes := make([]byte, l) 24 | for i := 0; i < l; i++ { 25 | bytes[i] = byte(randInt(65, 90)) 26 | } 27 | return string(bytes) 28 | } 29 | 30 | func OCR_RPC(rabbitMQ string, routingKey string, req rpc.OCRImageRequest) (res rpc.OCRImageResponse, err error) { 31 | conn, err := amqp.Dial(rabbitMQ) 32 | failOnError(err, "Failed to connect to RabbitMQ") 33 | defer conn.Close() 34 | 35 | ch, err := conn.Channel() 36 | failOnError(err, "Failed to open a channel") 37 | defer ch.Close() 38 | 39 | q, err := ch.QueueDeclare( 40 | "", // name 41 | false, // durable 42 | false, // delete when unused 43 | true, // exclusive 44 | false, // noWait 45 | nil, // arguments 46 | ) 47 | failOnError(err, "Failed to declare a queue") 48 | 49 | msgs, err := ch.Consume( 50 | q.Name, // queue 51 | "", // consumer 52 | true, // auto-ack 53 | false, // exclusive 54 | false, // no-local 55 | false, // no-wait 56 | nil, // args 57 | ) 58 | failOnError(err, "Failed to register a consumer") 59 | 60 | data, err := req.Encode() 61 | if err != nil { 62 | return 63 | } 64 | 65 | corrId := randomString(32) 66 | err = ch.Publish( 67 | "", // exchange 68 | routingKey, // routing key 69 | false, // mandatory 70 | false, // immediate 71 | amqp.Publishing{ 72 | ContentType: "text/plain", 73 | CorrelationId: corrId, 74 | ReplyTo: q.Name, 75 | Body: data, 76 | }) 77 | failOnError(err, "Failed to publish a message") 78 | 79 | for d := range msgs { 80 | if corrId == d.CorrelationId { 81 | err = res.Decode(d.Body) 82 | break 83 | } 84 | } 85 | 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | rabbitmq: 4 | image: rabbitmq:latest 5 | command: rabbitmq-server 6 | ports: 7 | - "5672:5672" 8 | nginx: 9 | image: nginx:latest 10 | command: nginx -g "daemon off;" 11 | volumes: 12 | - ./nginx:/etc/nginx/conf.d 13 | ports: 14 | - "10008:10008" 15 | depends_on: 16 | - backend-A 17 | - backend-B 18 | worker-A: 19 | image: onestraw/ocrservice 20 | command: worker --rabbitmq=amqp://guest:guest@rabbitmq:5672/ 21 | depends_on: 22 | - rabbitmq 23 | worker-B: 24 | image: onestraw/ocrservice 25 | command: worker --rabbitmq=amqp://guest:guest@rabbitmq:5672/ 26 | depends_on: 27 | - rabbitmq 28 | backend-A: 29 | image: onestraw/ocrservice 30 | command: backend --addr=:10002 --rabbitmq=amqp://guest:guest@rabbitmq:5672/ 31 | ports: 32 | - "10002:10002" 33 | depends_on: 34 | - rabbitmq 35 | backend-B: 36 | image: onestraw/ocrservice 37 | command: backend --addr=:10003 --rabbitmq=amqp://guest:guest@rabbitmq:5672/ 38 | ports: 39 | - "10003:10003" 40 | depends_on: 41 | - rabbitmq 42 | frontend: 43 | image: onestraw/ocrservice 44 | command: frontend --backend.addr=nginx:10008 45 | ports: 46 | - "10001:10001" 47 | depends_on: 48 | - nginx 49 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | The basic code is copied from https://github.com/otiai10/ocrserver 2 | - rewrite with gin framework 3 | - separate frontend and backend 4 | -------------------------------------------------------------------------------- /frontend/app/assets/css/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | transition: all 0.1s; 3 | } 4 | 5 | /* Hero */ 6 | .hero.is-mono { 7 | background-color: #303030; 8 | } 9 | .hero.is-mono *:not(a) { 10 | color: #ffffff; 11 | } 12 | .hero.is-mono a:hover { 13 | color: #40a0fd; 14 | } 15 | 16 | /* Image Container */ 17 | .img-container { 18 | text-align: center; 19 | background-size: contain; 20 | } 21 | .img-container>img { 22 | display: block; 23 | object-fit: contain; 24 | } 25 | .img-container>img:not([src=""]) { 26 | width: 100%; 27 | } 28 | .img-container>img:not([src=""]) + p { 29 | display: none; 30 | } 31 | .img-container>img[src=""] { 32 | display: none; 33 | } 34 | -------------------------------------------------------------------------------- /frontend/app/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onestraw/ocrservice/18c56ddba6ede3d28a31d8d9dad84f32a68b524d/frontend/app/assets/favicon.png -------------------------------------------------------------------------------- /frontend/app/assets/js/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; // ES6 2 | window.onload = () => { 3 | 4 | var http = { 5 | post: (path, data) => { 6 | return new Promise((resolve, reject) => { 7 | var xhr = new XMLHttpRequest(); 8 | xhr.open("POST", path, true); 9 | xhr.onreadystatechange = () => { 10 | if (xhr.readyState == XMLHttpRequest.DONE) return resolve(xhr); 11 | }; 12 | xhr.send(data); 13 | }); 14 | } 15 | }; 16 | 17 | var ui = { 18 | output: document.getElementById("output"), 19 | image: document.querySelector("img#img"), 20 | btnFile: document.getElementById("by-file"), 21 | cancel: document.getElementById("cancel-input"), 22 | file: document.getElementById("file"), 23 | langs: document.querySelector("input[name=langs]"), 24 | whitelist: document.querySelector("input[name=whitelist]"), 25 | submit: document.getElementById("submit"), 26 | loading: document.querySelector("button#submit>span:first-child"), 27 | standby: document.querySelector("button#submit>span:last-child"), 28 | show: uri => ui.image.setAttribute("src", uri), 29 | clear: () => { ui.image.setAttribute("src", ""), ui.file.value = ''; }, 30 | start: () => { ui.loading.style.display = "block"; ui.standby.style.display = "none"; ui.submit.setAttribute("disabled", true); ui.output.innerText = "{}"; }, 31 | finish: () => { ui.loading.style.display = "none"; ui.standby.style.display = "block"; ui.submit.removeAttribute("disabled"); }, 32 | }; 33 | 34 | ui.file.addEventListener("change", ev => { 35 | if (!ev.target.files || !ev.target.files.length) return null; 36 | const r = new FileReader(); 37 | r.onload = e => ui.show(e.target.result); 38 | r.readAsDataURL(ev.target.files[0]); 39 | }); 40 | ui.btnFile.addEventListener("click", () => ui.file.click()); 41 | ui.cancel.addEventListener("click", () => ui.clear()); 42 | ui.submit.addEventListener("click", () => { 43 | ui.start(); 44 | const req = generateRequest(); 45 | if (!req) return ui.finish(); 46 | http.post(req.path, req.data).then(xhr => { 47 | ui.output.innerText = `${xhr.status} ${xhr.statusText}\n-----\n${xhr.response}`; 48 | ui.finish(); 49 | }).catch(() => ui.finish()); 50 | }) 51 | 52 | var generateRequest = () => { 53 | var req = {path: "", data: null}; 54 | if (ui.file.files && ui.file.files.length != 0) { 55 | req.path = "/file"; 56 | req.data = new FormData(); 57 | if (ui.langs.value) req.data.append("languages", ui.langs.value); 58 | if (ui.whitelist.value) req.data.append("whitelist", ui.whitelist.value); 59 | req.data.append("file", ui.file.files[0]); 60 | } else { 61 | return window.alert("no image input set"); 62 | } 63 | return req; 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /frontend/app/assets/page-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onestraw/ocrservice/18c56ddba6ede3d28a31d8d9dad84f32a68b524d/frontend/app/assets/page-image.png -------------------------------------------------------------------------------- /frontend/app/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{.AppName}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 |

23 | Graphical API Client for OCRService 24 |

25 |

26 | or a working example of playing Tesseract/Gin/RabbitMQ/Nginx/Docker 27 |

28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |

Input

40 |

Image File

41 | 42 |
43 |
44 |
45 |
46 | 47 |

No Image Selected

48 |
49 |
50 |
51 | 56 |
57 | 58 |
59 | 60 |
61 |

Options

62 |

Optional flags

63 | 64 |
65 | 66 |
67 | 68 |
69 |

Comma serapated language codes

70 |
71 | 72 |
73 | 74 |
75 | 76 |
77 |

You can specify strict chars to be detected

78 |
79 | 80 |
81 |
82 | 83 |
84 |
85 | 95 |
96 |
97 |
98 |
99 |

Output

100 |
{}
101 |
102 |
103 |
104 | 105 |
106 | 107 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /frontend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "net/http" 6 | "net/http/httputil" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | var ( 12 | flagAddr = flag.String("addr", ":10001", "listening address") 13 | flagBackendAddr = flag.String("backend.addr", "127.0.0.1:10002", "listening address") 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | router := gin.Default() 19 | router.MaxMultipartMemory = 8 << 20 // 8 MiB 20 | 21 | router.LoadHTMLFiles("./app/views/index.html") 22 | router.Static("/assets", "./app/assets") 23 | router.GET("/", Index) 24 | router.GET("/status", Status) 25 | router.POST("/file", ReverseProxy()) 26 | 27 | router.Run(*flagAddr) 28 | } 29 | 30 | func Index(ctx *gin.Context) { 31 | ctx.HTML(http.StatusOK, "index.html", map[string]interface{}{ 32 | "AppName": "ocrservice", 33 | }) 34 | } 35 | 36 | func Status(ctx *gin.Context) { 37 | ctx.JSON(http.StatusOK, map[string]interface{}{ 38 | "message": "OK", 39 | }) 40 | } 41 | 42 | // ReverseProxy submit the ocr task to backend service 43 | func ReverseProxy() gin.HandlerFunc { 44 | target := *flagBackendAddr 45 | 46 | return func(c *gin.Context) { 47 | director := func(req *http.Request) { 48 | req.URL.Scheme = "http" 49 | req.URL.Host = target 50 | req.URL.Path = "/ocrimage" 51 | } 52 | proxy := &httputil.ReverseProxy{Director: director} 53 | proxy.ServeHTTP(c.Writer, c.Request) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /nginx/ocrimage.conf: -------------------------------------------------------------------------------- 1 | upstream ocrimage_pool { 2 | server backend-A:10002; 3 | server backend-B:10003; 4 | } 5 | 6 | server { 7 | listen 10008; 8 | server_name localhost; 9 | 10 | location /ocrimage { 11 | proxy_set_header Host $host; 12 | proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; 13 | proxy_pass http://ocrimage_pool; 14 | access_log /var/log/nginx/ocrimage.access.log; 15 | error_log /var/log/nginx/ocrimage.error.log; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ocrservice-ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onestraw/ocrservice/18c56ddba6ede3d28a31d8d9dad84f32a68b524d/ocrservice-ui.jpg -------------------------------------------------------------------------------- /rpc/types.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | type OCRImageRequest struct { 9 | Lang string 10 | Whitelist string 11 | Image []byte 12 | } 13 | 14 | type OCRImageResponse struct { 15 | Version string 16 | Text string 17 | } 18 | 19 | func Encode(object interface{}) ([]byte, error) { 20 | var buf bytes.Buffer 21 | encoder := gob.NewEncoder(&buf) 22 | err := encoder.Encode(object) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return buf.Bytes(), nil 27 | } 28 | 29 | func (r *OCRImageRequest) Encode() ([]byte, error) { 30 | return Encode(*r) 31 | } 32 | 33 | func (r *OCRImageRequest) Decode(data []byte) error { 34 | decoder := gob.NewDecoder(bytes.NewReader(data)) 35 | return decoder.Decode(r) 36 | } 37 | 38 | func (r *OCRImageResponse) Encode() ([]byte, error) { 39 | return Encode(*r) 40 | } 41 | 42 | func (r *OCRImageResponse) Decode(data []byte) error { 43 | decoder := gob.NewDecoder(bytes.NewReader(data)) 44 | return decoder.Decode(r) 45 | } 46 | -------------------------------------------------------------------------------- /tests/runtest.sh: -------------------------------------------------------------------------------- 1 | curl -H "Content-Type: multipart/form-data" -X POST http://localhost:10008/ocrimage -F "languages=eng" -F "whitelist=" -F "file=@test_eng.jpg" 2 | echo '\n------------------\n' 3 | curl -H "Content-Type: multipart/form-data" -X POST http://localhost:10008/ocrimage -F "languages=chi_sim" -F "whitelist=" -F "file=@test_chi_sim.jpg" 4 | echo '\n------------------\n' 5 | curl -H "Content-Type: multipart/form-data" -X POST http://localhost:10008/ocrimage -F "languages=chi_tra" -F "whitelist=" -F "file=@test_chi_tra.jpg" 6 | -------------------------------------------------------------------------------- /tests/test_chi_sim.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onestraw/ocrservice/18c56ddba6ede3d28a31d8d9dad84f32a68b524d/tests/test_chi_sim.jpg -------------------------------------------------------------------------------- /tests/test_chi_tra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onestraw/ocrservice/18c56ddba6ede3d28a31d8d9dad84f32a68b524d/tests/test_chi_tra.jpg -------------------------------------------------------------------------------- /tests/test_eng.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onestraw/ocrservice/18c56ddba6ede3d28a31d8d9dad84f32a68b524d/tests/test_eng.jpg -------------------------------------------------------------------------------- /worker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "io/ioutil" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | "github.com/otiai10/gosseract" 11 | log "github.com/sirupsen/logrus" 12 | "github.com/streadway/amqp" 13 | 14 | "github.com/onestraw/ocrservice/rpc" 15 | ) 16 | 17 | const version = "0.2.0" 18 | 19 | var ( 20 | flagRabbitMQ = flag.String("rabbitmq", "amqp://guest:guest@localhost:5672/", "RabbitMQ Address") 21 | flagQueueName = flag.String("queue_name", "ocrimage", "Queue name for OCR image") 22 | ) 23 | 24 | func failOnError(err error, msg string) { 25 | if err != nil { 26 | log.Fatalf("%s: %s", msg, err) 27 | } 28 | } 29 | 30 | func worker(data []byte) ([]byte, error) { 31 | var req rpc.OCRImageRequest 32 | err := req.Decode(data) 33 | if err != nil { 34 | log.Errorf("Decode OCRImageRequest error: %v", err) 35 | return nil, err 36 | } 37 | 38 | tempfile, err := ioutil.TempFile("", "ocrserver"+"-") 39 | if err != nil { 40 | return nil, err 41 | } 42 | defer func() { 43 | tempfile.Close() 44 | os.Remove(tempfile.Name()) 45 | }() 46 | 47 | if _, err := tempfile.Write(req.Image); err != nil { 48 | return nil, err 49 | } 50 | 51 | client := gosseract.NewClient() 52 | defer client.Close() 53 | 54 | client.SetImage(tempfile.Name()) 55 | client.Languages = []string{"eng"} 56 | if langs := req.Lang; langs != "" { 57 | client.Languages = strings.Split(langs, ",") 58 | } 59 | if whitelist := req.Whitelist; whitelist != "" { 60 | client.SetWhitelist(whitelist) 61 | } 62 | 63 | text, err := client.Text() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | resp := rpc.OCRImageResponse{ 69 | Version: version, 70 | Text: text, 71 | } 72 | return resp.Encode() 73 | } 74 | 75 | func main() { 76 | flag.Parse() 77 | 78 | var err error 79 | var conn *amqp.Connection 80 | maxTryNum := 9 81 | waitSecond := 1 82 | for i := 1; i <= maxTryNum; i++ { 83 | time.Sleep(time.Duration(waitSecond) * time.Second) 84 | log.Infof("#%d Try to connect to RabbitMQ...", i) 85 | conn, err = amqp.Dial(*flagRabbitMQ) 86 | if err == nil { 87 | break 88 | } 89 | log.Errorf("Failed to connect to RabbitMQ, error: %v", err) 90 | waitSecond *= 2 91 | } 92 | if conn == nil { 93 | log.Errorf("Failed to connect to RabbitMQ %d times, exit", maxTryNum) 94 | return 95 | } 96 | defer conn.Close() 97 | 98 | ch, err := conn.Channel() 99 | failOnError(err, "Failed to open a channel") 100 | defer ch.Close() 101 | 102 | q, err := ch.QueueDeclare( 103 | *flagQueueName, // name 104 | false, // durable 105 | false, // delete when unused 106 | false, // exclusive 107 | false, // no-wait 108 | nil, // arguments 109 | ) 110 | failOnError(err, "Failed to declare a queue") 111 | 112 | err = ch.Qos( 113 | 1, // prefetch count 114 | 0, // prefetch size 115 | false, // global 116 | ) 117 | failOnError(err, "Failed to set QoS") 118 | 119 | msgs, err := ch.Consume( 120 | q.Name, // queue 121 | "", // consumer 122 | false, // auto-ack 123 | false, // exclusive 124 | false, // no-local 125 | false, // no-wait 126 | nil, // args 127 | ) 128 | failOnError(err, "Failed to register a consumer") 129 | 130 | forever := make(chan bool) 131 | 132 | go func() { 133 | for d := range msgs { 134 | log.Infof("Get a task...") 135 | resp, err := worker(d.Body) 136 | if err != nil { 137 | log.Errorf("Worker errror: %v", err) 138 | } 139 | 140 | err = ch.Publish( 141 | "", // exchange 142 | d.ReplyTo, // routing key 143 | false, // mandatory 144 | false, // immediate 145 | amqp.Publishing{ 146 | ContentType: "text/plain", 147 | CorrelationId: d.CorrelationId, 148 | Body: resp, 149 | }) 150 | failOnError(err, "Failed to publish a message") 151 | 152 | d.Ack(false) 153 | } 154 | }() 155 | 156 | log.Printf(" [*] Awaiting RPC requests") 157 | <-forever 158 | } 159 | --------------------------------------------------------------------------------