mirror of https://github.com/tenox7/wrp.git
Compare commits
5 Commits
9b76c045d6
...
ecb2cc0c06
Author | SHA1 | Date |
---|---|---|
Antoni Sawicki | ecb2cc0c06 | |
Antoni Sawicki | b571df7a37 | |
Antoni Sawicki | 78c568ac09 | |
Antoni Sawicki | 04a755749e | |
Antoni Sawicki | e983f244f8 |
|
@ -14,7 +14,7 @@ A browser-in-browser "proxy" server that allows to use historical / vintage web
|
|||
* Scroll web page by clicking on the in-image scroll bar.
|
||||
* WRP also allows **a single tall image without the vertical scrollbar** and use client scrolling. To enable this, simply height **H** to `0` . However this should not be used with old and low spec clients. Such tall images will be very large, take a lot of memory and long time to process, especially for GIFs.
|
||||
* Do not use client browser history-back, instead use **Bk** button in the app.
|
||||
* You can re-capture page screenshot without reloading by using **St** (Stop).
|
||||
* You can re-capture page screenshot without reloading by using **St** (Stop). This is useful if page didn't render fully before screenshot is taken.
|
||||
* You can also reload and re-capture current page with **Re** (Reload).
|
||||
* To send keystrokes, fill **K** input box and press **Go**. There also are buttons for backspace, enter and arrow keys.
|
||||
* Prefer PNG over GIF if your browser supports it. PNG is much faster, whereas GIF requires a lot of additional processing on both client and server to encode/decode.
|
||||
|
|
134
wrp.go
134
wrp.go
|
@ -18,6 +18,7 @@ import (
|
|||
"image/color/palette"
|
||||
"image/gif"
|
||||
"image/png"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
|
@ -39,18 +40,23 @@ import (
|
|||
"github.com/soniakeys/quant/median"
|
||||
)
|
||||
|
||||
const version = "4.6.0"
|
||||
|
||||
var (
|
||||
version = "4.6.0"
|
||||
srv http.Server
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
img = make(map[string]bytes.Buffer)
|
||||
ismap = make(map[string]wrpReq)
|
||||
noDel bool
|
||||
defType string
|
||||
defGeom geom
|
||||
htmlTmpl *template.Template
|
||||
delay time.Duration
|
||||
addr = flag.String("l", ":8080", "Listen address:port, default :8080")
|
||||
headless = flag.Bool("h", true, "Headless mode / hide browser window (default true)")
|
||||
noDel = flag.Bool("n", false, "Do not free maps and images after use")
|
||||
defType = flag.String("t", "gif", "Image type: gif|png")
|
||||
fgeom = flag.String("g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
|
||||
htmFnam = flag.String("ui", "wrp.html", "HTML template file for the UI")
|
||||
delay = flag.Duration("s", 2*time.Second, "Delay/sleep after page is rendered and before screenshot is taken")
|
||||
srv http.Server
|
||||
actx, ctx context.Context
|
||||
acncl, cncl context.CancelFunc
|
||||
img = make(map[string]bytes.Buffer)
|
||||
ismap = make(map[string]wrpReq)
|
||||
defGeom geom
|
||||
htmlTmpl *template.Template
|
||||
)
|
||||
|
||||
//go:embed *.html
|
||||
|
@ -132,7 +138,7 @@ func (rq *wrpReq) parseForm() {
|
|||
rq.buttons = rq.r.FormValue("Fn")
|
||||
rq.imgType = rq.r.FormValue("t")
|
||||
if rq.imgType != "gif" && rq.imgType != "png" {
|
||||
rq.imgType = defType
|
||||
rq.imgType = *defType
|
||||
}
|
||||
log.Printf("%s WrpReq from UI Form: %+v\n", rq.r.RemoteAddr, rq)
|
||||
}
|
||||
|
@ -206,19 +212,26 @@ func (rq *wrpReq) action() chromedp.Action {
|
|||
return chromedp.Navigate(rq.url)
|
||||
}
|
||||
|
||||
// Process Keyboard and Mouse events or Navigate to the desired URL.
|
||||
// Navigate to the desired URL.
|
||||
func (rq *wrpReq) navigate() {
|
||||
err := chromedp.Run(ctx, rq.action())
|
||||
if err != nil {
|
||||
if err.Error() == "context canceled" {
|
||||
log.Printf("%s Contex cancelled, try again", rq.r.RemoteAddr)
|
||||
fmt.Fprintf(rq.w, "<BR>%s<BR> -- restarting, try again", err)
|
||||
ctx, cancel = chromedp.NewContext(context.Background())
|
||||
return
|
||||
}
|
||||
log.Printf("%s %s", rq.r.RemoteAddr, err)
|
||||
fmt.Fprintf(rq.w, "<BR>%s<BR>", err)
|
||||
ctxErr(chromedp.Run(ctx, rq.action()), rq.w)
|
||||
}
|
||||
|
||||
// Handle context errors
|
||||
func ctxErr(err error, w io.Writer) {
|
||||
// TODO: callers should have retry logic, perhaps create another function
|
||||
// that takes ...chromedp.Action and retries with give up
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
log.Printf("Context error: %s", err)
|
||||
fmt.Fprintf(w, "Context error: %s<BR>\n", err)
|
||||
if err.Error() != "context canceled" {
|
||||
return
|
||||
}
|
||||
ctx, cncl = chromedp.NewContext(actx)
|
||||
log.Printf("Created new context, try again")
|
||||
fmt.Fprintln(w, "Created new context, try again")
|
||||
}
|
||||
|
||||
// https://github.com/chromedp/chromedp/issues/979
|
||||
|
@ -278,7 +291,6 @@ func gifPalette(i image.Image, n int64) image.Image {
|
|||
|
||||
// Capture currently rendered web page to an image and fake ISMAP
|
||||
func (rq *wrpReq) capture() {
|
||||
var err error
|
||||
var styles []*css.ComputedStyleProperty
|
||||
var r, g, b int
|
||||
var h int64
|
||||
|
@ -307,21 +319,10 @@ func (rq *wrpReq) capture() {
|
|||
}
|
||||
chromedp.Run(
|
||||
ctx, emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), height, rq.zoom, false),
|
||||
chromedp.Sleep(delay), // TODO(tenox): find a better way to determine if page is rendered
|
||||
chromedp.Sleep(*delay), // TODO(tenox): find a better way to determine if page is rendered
|
||||
)
|
||||
// Capture screenshot...
|
||||
err = chromedp.Run(ctx, chromedpCaptureScreenshot(&pngcap, rq.height))
|
||||
if err != nil {
|
||||
if err.Error() == "context canceled" {
|
||||
log.Printf("%s Contex cancelled, try again", rq.r.RemoteAddr)
|
||||
fmt.Fprintf(rq.w, "<BR>%s<BR> -- restarting, try again", err)
|
||||
ctx, cancel = chromedp.NewContext(context.Background())
|
||||
return
|
||||
}
|
||||
log.Printf("%s Failed to capture screenshot: %s\n", rq.r.RemoteAddr, err)
|
||||
fmt.Fprintf(rq.w, "<BR>Unable to capture screenshot:<BR>%s<BR>\n", err)
|
||||
return
|
||||
}
|
||||
ctxErr(chromedp.Run(ctx, chromedpCaptureScreenshot(&pngcap, rq.height)), rq.w)
|
||||
seq := rand.Intn(9999)
|
||||
imgpath := fmt.Sprintf("/img/%04d.%s", seq, rq.imgType)
|
||||
mappath := fmt.Sprintf("/map/%04d.map", seq)
|
||||
|
@ -382,7 +383,7 @@ func pageServer(w http.ResponseWriter, r *http.Request) {
|
|||
rq.printHTML(printParams{bgColor: "#FFFFFF"})
|
||||
return
|
||||
}
|
||||
rq.navigate()
|
||||
rq.navigate() // TODO: if error from navigate do not capture
|
||||
rq.capture()
|
||||
}
|
||||
|
||||
|
@ -397,7 +398,7 @@ func mapServer(w http.ResponseWriter, r *http.Request) {
|
|||
log.Printf("Unable to find map %s\n", r.URL.Path)
|
||||
return
|
||||
}
|
||||
if !noDel {
|
||||
if !*noDel {
|
||||
defer delete(ismap, r.URL.Path)
|
||||
}
|
||||
n, err := fmt.Sscanf(r.URL.RawQuery, "%d,%d", &rq.mouseX, &rq.mouseY)
|
||||
|
@ -411,7 +412,7 @@ func mapServer(w http.ResponseWriter, r *http.Request) {
|
|||
rq.printHTML(printParams{bgColor: "#FFFFFF"})
|
||||
return
|
||||
}
|
||||
rq.navigate()
|
||||
rq.navigate() // TODO: if error from navigate do not capture
|
||||
rq.capture()
|
||||
}
|
||||
|
||||
|
@ -424,7 +425,7 @@ func imgServer(w http.ResponseWriter, r *http.Request) {
|
|||
log.Printf("%s Unable to find image %s\n", r.RemoteAddr, r.URL.Path)
|
||||
return
|
||||
}
|
||||
if !noDel {
|
||||
if !*noDel {
|
||||
defer delete(img, r.URL.Path)
|
||||
}
|
||||
switch {
|
||||
|
@ -451,7 +452,8 @@ func haltServer(w http.ResponseWriter, r *http.Request) {
|
|||
fmt.Fprintf(w, "Shutting down WRP...\n")
|
||||
w.(http.Flusher).Flush()
|
||||
time.Sleep(time.Second * 2)
|
||||
cancel()
|
||||
cncl()
|
||||
acncl()
|
||||
srv.Shutdown(context.Background())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -463,11 +465,13 @@ func tmpl(t string) string {
|
|||
if err != nil {
|
||||
goto builtin
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
tmpl, err = ioutil.ReadAll(fh)
|
||||
if err != nil {
|
||||
goto builtin
|
||||
}
|
||||
log.Printf("Got UI template from %v file\n", t)
|
||||
log.Printf("Got HTML UI template from %v file, size %v \n", t, len(tmpl))
|
||||
return string(tmpl)
|
||||
|
||||
builtin:
|
||||
|
@ -475,51 +479,36 @@ builtin:
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fhs.Close()
|
||||
|
||||
tmpl, err = ioutil.ReadAll(fhs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Got UI template from built-in\n")
|
||||
log.Printf("Got HTML UI template from embed\n")
|
||||
return string(tmpl)
|
||||
}
|
||||
|
||||
// Main...
|
||||
func main() {
|
||||
var addr, fgeom, tHTML string
|
||||
var headless bool
|
||||
var debug bool
|
||||
var err error
|
||||
flag.StringVar(&addr, "l", ":8080", "Listen address:port, default :8080")
|
||||
flag.BoolVar(&headless, "h", true, "Headless mode - hide browser window")
|
||||
flag.BoolVar(&debug, "d", false, "Debug ChromeDP")
|
||||
flag.BoolVar(&noDel, "n", false, "Do not free maps and images after use")
|
||||
flag.StringVar(&defType, "t", "gif", "Image type: gif|png")
|
||||
flag.StringVar(&fgeom, "g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
|
||||
flag.StringVar(&tHTML, "ui", "wrp.html", "HTML template file for the UI")
|
||||
flag.DurationVar(&delay, "s", 2*time.Second, "Delay/sleep after page is rendered and before screenshot is taken")
|
||||
flag.Parse()
|
||||
if len(os.Getenv("PORT")) > 0 {
|
||||
addr = ":" + os.Getenv(("PORT"))
|
||||
*addr = ":" + os.Getenv(("PORT"))
|
||||
}
|
||||
n, err := fmt.Sscanf(fgeom, "%dx%dx%d", &defGeom.w, &defGeom.h, &defGeom.c)
|
||||
n, err := fmt.Sscanf(*fgeom, "%dx%dx%d", &defGeom.w, &defGeom.h, &defGeom.c)
|
||||
if err != nil || n != 3 {
|
||||
log.Fatalf("Unable to parse -g geometry flag / %s", err)
|
||||
}
|
||||
|
||||
opts := append(chromedp.DefaultExecAllocatorOptions[:],
|
||||
chromedp.Flag("headless", headless),
|
||||
chromedp.Flag("headless", *headless),
|
||||
chromedp.Flag("hide-scrollbars", false),
|
||||
)
|
||||
actx, acancel := chromedp.NewExecAllocator(context.Background(), opts...)
|
||||
defer acancel()
|
||||
switch debug {
|
||||
case true:
|
||||
ctx, cancel = chromedp.NewContext(actx, chromedp.WithDebugf(log.Printf))
|
||||
default:
|
||||
ctx, cancel = chromedp.NewContext(actx)
|
||||
}
|
||||
defer cancel()
|
||||
actx, acncl = chromedp.NewExecAllocator(context.Background(), opts...)
|
||||
defer acncl()
|
||||
ctx, cncl = chromedp.NewContext(actx)
|
||||
defer cncl()
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
|
@ -528,7 +517,8 @@ func main() {
|
|||
go func() {
|
||||
<-c
|
||||
log.Printf("Interrupt - shutting down.")
|
||||
cancel()
|
||||
cncl()
|
||||
acncl()
|
||||
srv.Shutdown(context.Background())
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
@ -541,15 +531,15 @@ func main() {
|
|||
|
||||
log.Printf("Web Rendering Proxy Version %s\n", version)
|
||||
log.Printf("Args: %q", os.Args)
|
||||
log.Printf("Default Img Type: %v, Geometry: %+v", defType, defGeom)
|
||||
log.Printf("Default Img Type: %v, Geometry: %+v", *defType, defGeom)
|
||||
|
||||
htmlTmpl, err = template.New("wrp.html").Parse(tmpl(tHTML))
|
||||
htmlTmpl, err = template.New("wrp.html").Parse(tmpl(*htmFnam))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("Starting WRP http server on %s\n", addr)
|
||||
srv.Addr = addr
|
||||
log.Printf("Starting WRP http server on %s\n", *addr)
|
||||
srv.Addr = *addr
|
||||
err = srv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
Loading…
Reference in New Issue