backend.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "errors"
21 "net"
22 "net/http"
23 "net/rpc"
24 "os"
25 "os/exec"
26 "os/signal"
27 "strings"
28 "sync"
29 "syscall"
30
31 "humungus.tedunangst.com/r/webs/gate"
32 "humungus.tedunangst.com/r/webs/image"
33)
34
35type Shrinker struct {
36}
37
38type ShrinkerArgs struct {
39 Buf []byte
40 Params image.Params
41}
42
43type ShrinkerResult struct {
44 Image *image.Image
45}
46
47var shrinkgate = gate.NewLimiter(4)
48
49func (s *Shrinker) Shrink(args *ShrinkerArgs, res *ShrinkerResult) error {
50 shrinkgate.Start()
51 defer shrinkgate.Finish()
52 img, err := image.Vacuum(bytes.NewReader(args.Buf), args.Params)
53 if err != nil {
54 return err
55 }
56 res.Image = img
57 return nil
58}
59
60func backendSockname() string {
61 return dataDir + "/backend.sock"
62}
63
64func isSVG(data []byte) bool {
65 ct := http.DetectContentType(data)
66 if strings.HasPrefix(ct, "text/xml") {
67 return strings.Index(string(data), "<!DOCTYPE svg PUBLIC") != -1
68 }
69 if strings.HasPrefix(ct, "text/plain") {
70 return bytes.HasPrefix(data, []byte("<svg "))
71 }
72 return ct == "image/svg+xml"
73}
74
75func imageFromSVG(data []byte) (*image.Image, error) {
76 if len(data) > 100000 {
77 return nil, errors.New("my svg is too big")
78 }
79 svg := &image.Image{
80 Data: data,
81 Format: "svg+xml",
82 }
83 return svg, nil
84}
85
86func bigshrink(data []byte) (*image.Image, error) {
87 if isSVG(data) {
88 return imageFromSVG(data)
89 }
90 cl, err := rpc.Dial("unix", backendSockname())
91 if err != nil {
92 return nil, err
93 }
94 defer cl.Close()
95 var res ShrinkerResult
96 err = cl.Call("Shrinker.Shrink", &ShrinkerArgs{
97 Buf: data,
98 Params: image.Params{
99 LimitSize: 14200 * 4200,
100 MaxWidth: 2600,
101 MaxHeight: 2048,
102 MaxSize: 768 * 1024,
103 },
104 }, &res)
105 if err != nil {
106 return nil, err
107 }
108 return res.Image, nil
109}
110
111func shrinkit(data []byte) (*image.Image, error) {
112 if isSVG(data) {
113 return imageFromSVG(data)
114 }
115 cl, err := rpc.Dial("unix", backendSockname())
116 if err != nil {
117 return nil, err
118 }
119 defer cl.Close()
120 var res ShrinkerResult
121 err = cl.Call("Shrinker.Shrink", &ShrinkerArgs{
122 Buf: data,
123 Params: image.Params{LimitSize: 4200 * 4200, MaxWidth: 2048, MaxHeight: 2048},
124 }, &res)
125 if err != nil {
126 return nil, err
127 }
128 return res.Image, nil
129}
130
131var backendhooks []func()
132
133func orphancheck() {
134 var b [1]byte
135 os.Stdin.Read(b[:])
136 dlog.Printf("backend shutting down")
137 os.Exit(0)
138}
139
140func backendServer() {
141 dlog.Printf("backend server running")
142 go orphancheck()
143 signal.Ignore(syscall.SIGINT)
144 shrinker := new(Shrinker)
145 srv := rpc.NewServer()
146 err := srv.Register(shrinker)
147 if err != nil {
148 elog.Panicf("unable to register shrinker: %s", err)
149 }
150
151 sockname := backendSockname()
152 err = os.Remove(sockname)
153 if err != nil && !os.IsNotExist(err) {
154 elog.Panicf("unable to unlink socket: %s", err)
155 }
156
157 lis, err := net.Listen("unix", sockname)
158 if err != nil {
159 elog.Panicf("unable to register shrinker: %s", err)
160 }
161 err = setLimits()
162 if err != nil {
163 elog.Printf("error setting backend limits: %s", err)
164 }
165 for _, h := range backendhooks {
166 h()
167 }
168 srv.Accept(lis)
169}
170
171func runBackendServer() {
172 r, w, err := os.Pipe()
173 if err != nil {
174 elog.Panicf("can't pipe: %s", err)
175 }
176 proc := exec.Command(os.Args[0], reexecArgs("backend")...)
177 proc.Stdout = os.Stdout
178 proc.Stderr = os.Stderr
179 proc.Stdin = r
180 err = proc.Start()
181 if err != nil {
182 elog.Panicf("can't exec backend: %s", err)
183 }
184 workinprogress++
185 var mtx sync.Mutex
186 go func() {
187 <-endoftheworld
188 mtx.Lock()
189 defer mtx.Unlock()
190 w.Close()
191 w = nil
192 readyalready <- true
193 }()
194 go func() {
195 proc.Wait()
196 mtx.Lock()
197 defer mtx.Unlock()
198 if w != nil {
199 elog.Printf("lost the backend: %s", err)
200 w.Close()
201 }
202 }()
203}