// Package openutil displays Markdown or HTML in a new browser tab. package openutil import ( "log" "net" "net/http" "strings" "time" "github.com/shurcooL/github_flavored_markdown/gfmstyle" "github.com/shurcooL/go/gfmutil" "github.com/shurcooL/go/open" ) // DisplayMarkdownInBrowser displays given Markdown in a new browser window/tab. func DisplayMarkdownInBrowser(markdown []byte) { stopServerChan := make(chan struct{}) handler := func(w http.ResponseWriter, req *http.Request) { gfmutil.WriteGitHubFlavoredMarkdownViaLocal(w, markdown) // TODO: A better way to fix: /assets/gfm/gfm.css Failed to load resource: net::ERR_CONNECTION_REFUSED. // HACK: Give some time for other assets to finish loading. go func() { time.Sleep(1 * time.Second) stopServerChan <- struct{}{} }() } http.HandleFunc("/index", handler) http.Handle("/assets/gfm/", http.StripPrefix("/assets/gfm", http.FileServer(gfmstyle.Assets))) // Serve the "/assets/gfm/gfm.css" file. http.Handle("/favicon.ico", http.NotFoundHandler()) // TODO: Aquire a free port similarly to using ioutil.TempFile() for files. // TODO: Consider using httptest.NewServer. open.Open("http://localhost:7044/index") err := httpstoppable۰ListenAndServe("localhost:7044", nil, stopServerChan) if err != nil { panic(err) } } // DisplayHTMLInBrowser displays given html page in a new browser window/tab. // query can be empty, otherwise it should begin with "?" like "?key=value". func DisplayHTMLInBrowser(mux *http.ServeMux, stopServerChan <-chan struct{}, query string) { // TODO: Aquire a free port similarly to using ioutil.TempFile() for files. open.Open("http://localhost:7044/index" + query) err := httpstoppable۰ListenAndServe("localhost:7044", mux, stopServerChan) if err != nil { panic(err) } } // ListenAndServe listens on the TCP network address addr // and then calls Serve with handler to handle requests // on incoming connections. // Accepted connections are configured to enable TCP keep-alives. // Handler is typically nil, in which case the http.DefaultServeMux is // used. // // When receiving from stop unblocks (because it's closed or a value is sent), // listener is closed and ListenAndServe returns with nil error. // Otherise, it always returns a non-nil error. // // Deprecated: Go 1.8 added native support for stopping a server in net/http. // net/http should be used instead. This copied function will be removed soon. func httpstoppable۰ListenAndServe(addr string, handler http.Handler, stop <-chan struct{}) error { srv := &http.Server{Addr: addr, Handler: handler} if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } go func() { <-stop err := ln.Close() if err != nil { log.Println("httpstoppable.ListenAndServe: error closing listener:", err) } }() err = srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) switch { // Serve always returns a non-nil error. case strings.Contains(err.Error(), "use of closed network connection"): return nil default: return err } } // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. It's used by ListenAndServe so dead TCP connections // (e.g. closing laptop mid-download) eventually go away. type tcpKeepAliveListener struct { *net.TCPListener } func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { tc, err := ln.AcceptTCP() if err != nil { return } tc.SetKeepAlive(true) tc.SetKeepAlivePeriod(3 * time.Minute) return tc, nil }