all repos — honk @ v1.1.0

my fork of honk

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}