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 "strings"
27
28 "humungus.tedunangst.com/r/webs/gate"
29 "humungus.tedunangst.com/r/webs/image"
30)
31
32type Shrinker struct {
33}
34
35type ShrinkerArgs struct {
36 Buf []byte
37 Params image.Params
38}
39
40type ShrinkerResult struct {
41 Image *image.Image
42}
43
44var shrinkgate = gate.NewLimiter(4)
45
46func (s *Shrinker) Shrink(args *ShrinkerArgs, res *ShrinkerResult) error {
47 shrinkgate.Start()
48 defer shrinkgate.Finish()
49 img, err := image.Vacuum(bytes.NewReader(args.Buf), args.Params)
50 if err != nil {
51 return err
52 }
53 res.Image = img
54 return nil
55}
56
57func backendSockname() string {
58 return dataDir + "/backend.sock"
59}
60
61func isSVG(data []byte) bool {
62 ct := http.DetectContentType(data)
63 if strings.HasPrefix(ct, "text/xml") {
64 return strings.Index(string(data), "<!DOCTYPE svg PUBLIC") != -1
65 }
66 if strings.HasPrefix(ct, "text/plain") {
67 return bytes.HasPrefix(data, []byte("<svg "))
68 }
69 return ct == "image/svg+xml"
70}
71
72func imageFromSVG(data []byte) (*image.Image, error) {
73 if len(data) > 100000 {
74 return nil, errors.New("my svg is too big")
75 }
76 svg := &image.Image{
77 Data: data,
78 Format: "svg+xml",
79 }
80 return svg, nil
81}
82
83func bigshrink(data []byte) (*image.Image, error) {
84 if isSVG(data) {
85 return imageFromSVG(data)
86 }
87 cl, err := rpc.Dial("unix", backendSockname())
88 if err != nil {
89 return nil, err
90 }
91 defer cl.Close()
92 var res ShrinkerResult
93 err = cl.Call("Shrinker.Shrink", &ShrinkerArgs{
94 Buf: data,
95 Params: image.Params{
96 LimitSize: 14200 * 4200,
97 MaxWidth: 2600,
98 MaxHeight: 2048,
99 MaxSize: 768 * 1024,
100 },
101 }, &res)
102 if err != nil {
103 return nil, err
104 }
105 return res.Image, nil
106}
107
108func shrinkit(data []byte) (*image.Image, error) {
109 if isSVG(data) {
110 return imageFromSVG(data)
111 }
112 cl, err := rpc.Dial("unix", backendSockname())
113 if err != nil {
114 return nil, err
115 }
116 defer cl.Close()
117 var res ShrinkerResult
118 err = cl.Call("Shrinker.Shrink", &ShrinkerArgs{
119 Buf: data,
120 Params: image.Params{LimitSize: 4200 * 4200, MaxWidth: 2048, MaxHeight: 2048},
121 }, &res)
122 if err != nil {
123 return nil, err
124 }
125 return res.Image, nil
126}
127
128var backendhooks []func()
129
130func orphancheck() {
131 var b [1]byte
132 os.Stdin.Read(b[:])
133 dlog.Printf("backend shutting down")
134 os.Exit(0)
135}
136
137func backendServer() {
138 dlog.Printf("backend server running")
139 go orphancheck()
140 shrinker := new(Shrinker)
141 srv := rpc.NewServer()
142 err := srv.Register(shrinker)
143 if err != nil {
144 elog.Panicf("unable to register shrinker: %s", err)
145 }
146
147 sockname := backendSockname()
148 err = os.Remove(sockname)
149 if err != nil && !os.IsNotExist(err) {
150 elog.Panicf("unable to unlink socket: %s", err)
151 }
152
153 lis, err := net.Listen("unix", sockname)
154 if err != nil {
155 elog.Panicf("unable to register shrinker: %s", err)
156 }
157 err = setLimits()
158 if err != nil {
159 elog.Printf("error setting backend limits: %s", err)
160 }
161 for _, h := range backendhooks {
162 h()
163 }
164 srv.Accept(lis)
165}
166
167func runBackendServer() {
168 r, w, err := os.Pipe()
169 if err != nil {
170 elog.Panicf("can't pipe: %s", err)
171 }
172 proc := exec.Command(os.Args[0], reexecArgs("backend")...)
173 proc.Stdout = os.Stdout
174 proc.Stderr = os.Stderr
175 proc.Stdin = r
176 err = proc.Start()
177 if err != nil {
178 elog.Panicf("can't exec backend: %s", err)
179 }
180 go func() {
181 proc.Wait()
182 elog.Printf("lost the backend: %s", err)
183 w.Close()
184 }()
185}