all repos — honk @ 31fba5f708c51dd3c5f930fb887d480689cc2347

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	"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}