mirror of
https://github.com/tenox7/wrp.git
synced 2024-06-12 15:29:27 +00:00
Compare commits
4 Commits
ecb2cc0c06
...
d8f5c6fb28
Author | SHA1 | Date | |
---|---|---|---|
|
d8f5c6fb28 | ||
|
c816ef712a | ||
|
d28924583c | ||
|
1c17b39ea5 |
|
@ -17,7 +17,7 @@ A browser-in-browser "proxy" server that allows to use historical / vintage web
|
||||||
* 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 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).
|
* 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.
|
* 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.
|
* 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. Jpeg encoding is also quite fast.
|
||||||
* GIF images are by default encoded with 216 colors, "web safe" palette. This uses an ultra fast but not very accurate color mapping algorithm. If you want better color representation switch to 256 color mode.
|
* GIF images are by default encoded with 216 colors, "web safe" palette. This uses an ultra fast but not very accurate color mapping algorithm. If you want better color representation switch to 256 color mode.
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
@ -54,12 +54,13 @@ Fortunately ACI allows port 80 without encryption.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
-l listen address:port (default :8080)
|
-l listen address:port (default :8080)
|
||||||
-t image type gif or png (default gif)
|
-t image type gif, png or jpg (default gif)
|
||||||
-g image geometry, WxHxC, height can be 0 for unlimited (default 1152x600x216)
|
-g image geometry, WxHxC, height can be 0 for unlimited (default 1152x600x216)
|
||||||
C (number of colors) is only used for GIF
|
C (number of colors) is only used for GIF
|
||||||
|
-q Jpeg image quality, default 80%
|
||||||
-h headless mode, hide browser window on the server (default true)
|
-h headless mode, hide browser window on the server (default true)
|
||||||
-d chromedp debug logging (default false)
|
-d chromedp debug logging (default false)
|
||||||
-n do not free maps and gif images after use (default false)
|
-n do not free maps and images after use (default false)
|
||||||
-ui html template file (default "wrp.html")
|
-ui html template file (default "wrp.html")
|
||||||
-s delay/sleep after page is rendered before screenshot is taken (default 2s)
|
-s delay/sleep after page is rendered before screenshot is taken (default 2s)
|
||||||
```
|
```
|
||||||
|
@ -86,7 +87,7 @@ used with PNG and lots of memory on a client side.
|
||||||
|
|
||||||
**Z** is zoom or scale
|
**Z** is zoom or scale
|
||||||
|
|
||||||
**C** is colors, for GIF images only (unused in PNG)
|
**C** is colors, for GIF images only (unused in PNG, JPG)
|
||||||
|
|
||||||
**K** is keystroke input, you can type some letters in it and when you click Go it will be typed in the remote browser.
|
**K** is keystroke input, you can type some letters in it and when you click Go it will be typed in the remote browser.
|
||||||
|
|
||||||
|
|
132
wrp.go
132
wrp.go
|
@ -17,12 +17,14 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color/palette"
|
"image/color/palette"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -46,7 +48,8 @@ var (
|
||||||
addr = flag.String("l", ":8080", "Listen address:port, default :8080")
|
addr = flag.String("l", ":8080", "Listen address:port, default :8080")
|
||||||
headless = flag.Bool("h", true, "Headless mode / hide browser window (default true)")
|
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")
|
noDel = flag.Bool("n", false, "Do not free maps and images after use")
|
||||||
defType = flag.String("t", "gif", "Image type: gif|png")
|
defType = flag.String("t", "gif", "Image type: png|gif|jpg")
|
||||||
|
jpgQual = flag.Int("q", 80, "Jpeg image quality, default 80%")
|
||||||
fgeom = flag.String("g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
|
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")
|
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")
|
delay = flag.Duration("s", 2*time.Second, "Delay/sleep after page is rendered and before screenshot is taken")
|
||||||
|
@ -137,7 +140,11 @@ func (rq *wrpReq) parseForm() {
|
||||||
rq.keys = rq.r.FormValue("k")
|
rq.keys = rq.r.FormValue("k")
|
||||||
rq.buttons = rq.r.FormValue("Fn")
|
rq.buttons = rq.r.FormValue("Fn")
|
||||||
rq.imgType = rq.r.FormValue("t")
|
rq.imgType = rq.r.FormValue("t")
|
||||||
if rq.imgType != "gif" && rq.imgType != "png" {
|
switch rq.imgType {
|
||||||
|
case "png":
|
||||||
|
case "gif":
|
||||||
|
case "jpg":
|
||||||
|
default:
|
||||||
rq.imgType = *defType
|
rq.imgType = *defType
|
||||||
}
|
}
|
||||||
log.Printf("%s WrpReq from UI Form: %+v\n", rq.r.RemoteAddr, rq)
|
log.Printf("%s WrpReq from UI Form: %+v\n", rq.r.RemoteAddr, rq)
|
||||||
|
@ -294,7 +301,7 @@ func (rq *wrpReq) capture() {
|
||||||
var styles []*css.ComputedStyleProperty
|
var styles []*css.ComputedStyleProperty
|
||||||
var r, g, b int
|
var r, g, b int
|
||||||
var h int64
|
var h int64
|
||||||
var pngcap []byte
|
var pngCap []byte
|
||||||
chromedp.Run(ctx,
|
chromedp.Run(ctx,
|
||||||
emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), 10, rq.zoom, false),
|
emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), 10, rq.zoom, false),
|
||||||
chromedp.Location(&rq.url),
|
chromedp.Location(&rq.url),
|
||||||
|
@ -322,51 +329,71 @@ func (rq *wrpReq) capture() {
|
||||||
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...
|
// Capture screenshot...
|
||||||
ctxErr(chromedp.Run(ctx, chromedpCaptureScreenshot(&pngcap, rq.height)), rq.w)
|
ctxErr(chromedp.Run(ctx, chromedpCaptureScreenshot(&pngCap, rq.height)), rq.w)
|
||||||
seq := rand.Intn(9999)
|
seq := rand.Intn(9999)
|
||||||
imgpath := fmt.Sprintf("/img/%04d.%s", seq, rq.imgType)
|
imgPath := fmt.Sprintf("/img/%04d.%s", seq, rq.imgType)
|
||||||
mappath := fmt.Sprintf("/map/%04d.map", seq)
|
mapPath := fmt.Sprintf("/map/%04d.map", seq)
|
||||||
ismap[mappath] = *rq
|
ismap[mapPath] = *rq
|
||||||
var ssize string
|
var sSize string
|
||||||
var iw, ih int
|
var iW, iH int
|
||||||
switch rq.imgType {
|
switch rq.imgType {
|
||||||
|
case "png":
|
||||||
|
pngBuf := bytes.NewBuffer(pngCap)
|
||||||
|
img[imgPath] = *pngBuf
|
||||||
|
cfg, _, _ := image.DecodeConfig(pngBuf)
|
||||||
|
sSize = fmt.Sprintf("%.0f KB", float32(len(pngBuf.Bytes()))/1024.0)
|
||||||
|
iW = cfg.Width
|
||||||
|
iH = cfg.Height
|
||||||
|
log.Printf("%s Got PNG image: %s, Size: %s, Res: %dx%d\n", rq.r.RemoteAddr, imgPath, sSize, iW, iH)
|
||||||
case "gif":
|
case "gif":
|
||||||
i, err := png.Decode(bytes.NewReader(pngcap))
|
i, err := png.Decode(bytes.NewReader(pngCap))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
|
log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
|
||||||
fmt.Fprintf(rq.w, "<BR>Unable to decode page PNG screenshot:<BR>%s<BR>\n", err)
|
fmt.Fprintf(rq.w, "<BR>Unable to decode page PNG screenshot:<BR>%s<BR>\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st := time.Now()
|
st := time.Now()
|
||||||
var gifbuf bytes.Buffer
|
var gifBuf bytes.Buffer
|
||||||
err = gif.Encode(&gifbuf, gifPalette(i, rq.colors), &gif.Options{})
|
err = gif.Encode(&gifBuf, gifPalette(i, rq.colors), &gif.Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("%s Failed to encode GIF: %s\n", rq.r.RemoteAddr, err)
|
log.Printf("%s Failed to encode GIF: %s\n", rq.r.RemoteAddr, err)
|
||||||
fmt.Fprintf(rq.w, "<BR>Unable to encode GIF:<BR>%s<BR>\n", err)
|
fmt.Fprintf(rq.w, "<BR>Unable to encode GIF:<BR>%s<BR>\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
img[imgpath] = gifbuf
|
img[imgPath] = gifBuf
|
||||||
ssize = fmt.Sprintf("%.0f KB", float32(len(gifbuf.Bytes()))/1024.0)
|
sSize = fmt.Sprintf("%.0f KB", float32(len(gifBuf.Bytes()))/1024.0)
|
||||||
iw = i.Bounds().Max.X
|
iW = i.Bounds().Max.X
|
||||||
ih = i.Bounds().Max.Y
|
iH = i.Bounds().Max.Y
|
||||||
log.Printf("%s Encoded GIF image: %s, Size: %s, Colors: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgpath, ssize, rq.colors, iw, ih, time.Since(st).Milliseconds())
|
log.Printf("%s Encoded GIF image: %s, Size: %s, Colors: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgPath, sSize, rq.colors, iW, iH, time.Since(st).Milliseconds())
|
||||||
case "png":
|
case "jpg":
|
||||||
pngbuf := bytes.NewBuffer(pngcap)
|
i, err := png.Decode(bytes.NewReader(pngCap))
|
||||||
img[imgpath] = *pngbuf
|
if err != nil {
|
||||||
cfg, _, _ := image.DecodeConfig(pngbuf)
|
log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
|
||||||
ssize = fmt.Sprintf("%.0f KB", float32(len(pngbuf.Bytes()))/1024.0)
|
fmt.Fprintf(rq.w, "<BR>Unable to decode page PNG screenshot:<BR>%s<BR>\n", err)
|
||||||
iw = cfg.Width
|
return
|
||||||
ih = cfg.Height
|
}
|
||||||
log.Printf("%s Got PNG image: %s, Size: %s, Res: %dx%d\n", rq.r.RemoteAddr, imgpath, ssize, iw, ih)
|
st := time.Now()
|
||||||
|
var jpgBuf bytes.Buffer
|
||||||
|
err = jpeg.Encode(&jpgBuf, i, &jpeg.Options{Quality: *jpgQual})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s Failed to encode JPG: %s\n", rq.r.RemoteAddr, err)
|
||||||
|
fmt.Fprintf(rq.w, "<BR>Unable to encode JPG:<BR>%s<BR>\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img[imgPath] = jpgBuf
|
||||||
|
sSize = fmt.Sprintf("%.0f KB", float32(len(jpgBuf.Bytes()))/1024.0)
|
||||||
|
iW = i.Bounds().Max.X
|
||||||
|
iH = i.Bounds().Max.Y
|
||||||
|
log.Printf("%s Encoded JPG image: %s, Size: %s, Quality: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgPath, sSize, *jpgQual, iW, iH, time.Since(st).Milliseconds())
|
||||||
}
|
}
|
||||||
rq.printHTML(printParams{
|
rq.printHTML(printParams{
|
||||||
bgColor: fmt.Sprintf("#%02X%02X%02X", r, g, b),
|
bgColor: fmt.Sprintf("#%02X%02X%02X", r, g, b),
|
||||||
pageHeight: fmt.Sprintf("%d PX", h),
|
pageHeight: fmt.Sprintf("%d PX", h),
|
||||||
imgSize: ssize,
|
imgSize: sSize,
|
||||||
imgURL: imgpath,
|
imgURL: imgPath,
|
||||||
mapURL: mappath,
|
mapURL: mapPath,
|
||||||
imgWidth: iw,
|
imgWidth: iW,
|
||||||
imgHeight: ih,
|
imgHeight: iH,
|
||||||
})
|
})
|
||||||
log.Printf("%s Done with capture for %s\n", rq.r.RemoteAddr, rq.url)
|
log.Printf("%s Done with capture for %s\n", rq.r.RemoteAddr, rq.url)
|
||||||
}
|
}
|
||||||
|
@ -419,8 +446,8 @@ func mapServer(w http.ResponseWriter, r *http.Request) {
|
||||||
// Process HTTP requests for images '/img/' url
|
// Process HTTP requests for images '/img/' url
|
||||||
func imgServer(w http.ResponseWriter, r *http.Request) {
|
func imgServer(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("%s IMG Request for %s\n", r.RemoteAddr, r.URL.Path)
|
log.Printf("%s IMG Request for %s\n", r.RemoteAddr, r.URL.Path)
|
||||||
imgbuf, ok := img[r.URL.Path]
|
imgBuf, ok := img[r.URL.Path]
|
||||||
if !ok || imgbuf.Bytes() == nil {
|
if !ok || imgBuf.Bytes() == nil {
|
||||||
fmt.Fprintf(w, "Unable to find image %s\n", r.URL.Path)
|
fmt.Fprintf(w, "Unable to find image %s\n", r.URL.Path)
|
||||||
log.Printf("%s Unable to find image %s\n", r.RemoteAddr, r.URL.Path)
|
log.Printf("%s Unable to find image %s\n", r.RemoteAddr, r.URL.Path)
|
||||||
return
|
return
|
||||||
|
@ -433,12 +460,14 @@ func imgServer(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "image/gif")
|
w.Header().Set("Content-Type", "image/gif")
|
||||||
case strings.HasPrefix(r.URL.Path, ".png"):
|
case strings.HasPrefix(r.URL.Path, ".png"):
|
||||||
w.Header().Set("Content-Type", "image/png")
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
case strings.HasPrefix(r.URL.Path, ".jpg"):
|
||||||
|
w.Header().Set("Content-Type", "image/jpeg")
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(imgbuf.Bytes())))
|
w.Header().Set("Content-Length", strconv.Itoa(len(imgBuf.Bytes())))
|
||||||
w.Header().Set("Cache-Control", "max-age=0")
|
w.Header().Set("Cache-Control", "max-age=0")
|
||||||
w.Header().Set("Expires", "-1")
|
w.Header().Set("Expires", "-1")
|
||||||
w.Header().Set("Pragma", "no-cache")
|
w.Header().Set("Pragma", "no-cache")
|
||||||
w.Write(imgbuf.Bytes())
|
w.Write(imgBuf.Bytes())
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,13 +518,42 @@ builtin:
|
||||||
return string(tmpl)
|
return string(tmpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main...
|
// Print my own IP addresses
|
||||||
|
func printIPs(b string) {
|
||||||
|
ap := strings.Split(b, ":")
|
||||||
|
if len(ap) < 1 {
|
||||||
|
log.Fatal("Wrong format of ipaddress:port")
|
||||||
|
}
|
||||||
|
log.Printf("Listen address: %v", b)
|
||||||
|
if ap[0] != "" && ap[0] != "0.0.0.0" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
log.Print("Unable to get interfaces: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var m string
|
||||||
|
for _, i := range a {
|
||||||
|
n, ok := i.(*net.IPNet)
|
||||||
|
if !ok || n.IP.IsLoopback() || strings.Contains(n.IP.String(), ":") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m = m + n.IP.String() + " "
|
||||||
|
}
|
||||||
|
log.Print("My IP addresses: ", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
var err error
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
log.Printf("Web Rendering Proxy Version %s\n", version)
|
||||||
|
log.Printf("Args: %q", os.Args)
|
||||||
if len(os.Getenv("PORT")) > 0 {
|
if len(os.Getenv("PORT")) > 0 {
|
||||||
*addr = ":" + os.Getenv(("PORT"))
|
*addr = ":" + os.Getenv(("PORT"))
|
||||||
}
|
}
|
||||||
|
printIPs(*addr)
|
||||||
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 {
|
if err != nil || n != 3 {
|
||||||
log.Fatalf("Unable to parse -g geometry flag / %s", err)
|
log.Fatalf("Unable to parse -g geometry flag / %s", err)
|
||||||
|
@ -529,8 +587,6 @@ func main() {
|
||||||
http.HandleFunc("/shutdown/", haltServer)
|
http.HandleFunc("/shutdown/", haltServer)
|
||||||
http.HandleFunc("/favicon.ico", http.NotFound)
|
http.HandleFunc("/favicon.ico", http.NotFound)
|
||||||
|
|
||||||
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(*htmFnam))
|
htmlTmpl, err = template.New("wrp.html").Parse(tmpl(*htmFnam))
|
||||||
|
@ -538,7 +594,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Starting WRP http server on %s\n", *addr)
|
log.Print("Starting WRP http server")
|
||||||
srv.Addr = *addr
|
srv.Addr = *addr
|
||||||
err = srv.ListenAndServe()
|
err = srv.ListenAndServe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
3
wrp.html
3
wrp.html
|
@ -21,8 +21,9 @@
|
||||||
<OPTION VALUE="1.3" {{ if eq .Zoom 1.3}}SELECTED{{end}}>1.3 x</OPTION>
|
<OPTION VALUE="1.3" {{ if eq .Zoom 1.3}}SELECTED{{end}}>1.3 x</OPTION>
|
||||||
</SELECT>
|
</SELECT>
|
||||||
T <SELECT NAME="t">
|
T <SELECT NAME="t">
|
||||||
<OPTION VALUE="gif" {{ if eq .ImgType "gif"}}SELECTED{{end}}>GIF</OPTION>
|
|
||||||
<OPTION VALUE="png" {{ if eq .ImgType "png"}}SELECTED{{end}}>PNG</OPTION>
|
<OPTION VALUE="png" {{ if eq .ImgType "png"}}SELECTED{{end}}>PNG</OPTION>
|
||||||
|
<OPTION VALUE="gif" {{ if eq .ImgType "gif"}}SELECTED{{end}}>GIF</OPTION>
|
||||||
|
<OPTION VALUE="jpg" {{ if eq .ImgType "jpg"}}SELECTED{{end}}>JPG</OPTION>
|
||||||
</SELECT>
|
</SELECT>
|
||||||
C <SELECT NAME="c">
|
C <SELECT NAME="c">
|
||||||
<OPTION VALUE="256" {{ if eq .NColors 256}}SELECTED{{end}}>256</OPTION>
|
<OPTION VALUE="256" {{ if eq .NColors 256}}SELECTED{{end}}>256</OPTION>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user