Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cmd/playground/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,20 @@ func start(goRoot string, args appArgs) error {

r := mux.NewRouter()
pg := goplay.NewClient(args.playgroundUrl, goplay.DefaultUserAgent, 15*time.Second)

// API routes
langserver.New(Version, pg, packages, compiler.NewBuildService(zap.S(), store)).
Mount(r.PathPrefix("/api").Subrouter())
r.PathPrefix("/").Handler(langserver.SpaFileServer("./public"))

// Web UI routes
indexHandler := langserver.NewIndexFileServer("./public")
spaHandler := langserver.NewSpaFileServer("./public")
r.Path("/").
Handler(indexHandler)
r.Path("/snippet/{snippetID:[A-Za-z0-9_-]+}").
Handler(indexHandler)
r.PathPrefix("/").
Handler(spaHandler)

var handler http.Handler
if args.debug {
Expand Down
70 changes: 57 additions & 13 deletions pkg/langserver/spa.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,45 @@ import (
"strings"
)

// Advanced static server
type spaFileServer struct {
const (
IndexFileName = "index.html"
NotFoundFileName = "404.html"
)

type httpStatusInterceptor struct {
http.ResponseWriter
desiredStatus int
}

func (i httpStatusInterceptor) WriteHeader(_ int) {
i.ResponseWriter.WriteHeader(i.desiredStatus)
}

type IndexFileServer struct {
indexFilePath string
}

// NewIndexFileServer returns handler which serves index.html page from root.
func NewIndexFileServer(root http.Dir) *IndexFileServer {
return &IndexFileServer{
indexFilePath: filepath.Join(string(root), IndexFileName),
}
}

func (fs IndexFileServer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
http.ServeFile(rw, r, fs.indexFilePath)
}

// SpaFileServer is a wrapper around http.FileServer for serving SPA contents.
type SpaFileServer struct {
root http.Dir
NotFoundHandler func(http.ResponseWriter, *http.Request)
NotFoundHandler http.Handler
}

// ServeHTTP implements http.Handler
func (fs *spaFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (fs *SpaFileServer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if containsDotDot(r.URL.Path) {
Errorf(http.StatusNotFound, "Not Found").WriteResponse(w)
Errorf(http.StatusNotFound, "Not Found").WriteResponse(rw)
return
}

Expand All @@ -39,16 +68,14 @@ func (fs *spaFileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
name := path.Join(dir, filepath.FromSlash(upath))

//check if file exists
f, err := os.Open(name)
if err != nil {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
http.ServeFile(w, r, string(fs.root)+"/index.html")
fs.NotFoundHandler.ServeHTTP(rw, r)
return
}
}
defer f.Close()

http.ServeFile(w, r, name)
http.ServeFile(rw, r, name)
}

func containsDotDot(v string) bool {
Expand All @@ -65,7 +92,24 @@ func containsDotDot(v string) bool {

func isSlashRune(r rune) bool { return r == '/' || r == '\\' }

// SpaFileServer returns SPA handler
func SpaFileServer(root http.Dir) http.Handler {
return &spaFileServer{root: root}
// NewSpaFileServer returns SPA handler
func NewSpaFileServer(root http.Dir) *SpaFileServer {
notFoundHandler := NewFileServerWithStatus(filepath.Join(string(root), NotFoundFileName), http.StatusNotFound)
return &SpaFileServer{
NotFoundHandler: notFoundHandler,
root: root,
}
}

// NewFileServerWithStatus returns http.Handler which serves specified file with desired HTTP status
func NewFileServerWithStatus(name string, code int) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ServeFileWithStatus(rw, r, name, code)
}
}

// ServeFileWithStatus serves file in HTTP response with specified HTTP status.
func ServeFileWithStatus(rw http.ResponseWriter, r *http.Request, name string, code int) {
interceptor := httpStatusInterceptor{desiredStatus: code, ResponseWriter: rw}
http.ServeFile(interceptor, r, name)
}
203 changes: 203 additions & 0 deletions web/public/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🤔 404 Not Found!</title>
<style>
body {
background: #1f1f1f;
color: #f4f4f4;
margin: 0;
padding: 0;
font: 11pt system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.errorPage {
display: flex;
justify-content: center;
justify-items: center;
position: fixed;
inset: 0;
flex-direction: row-reverse;
}
.errorPage__container {
align-self: center;
width: 100%;
max-width: 480px;
}
.errorPage__statusCode {
font: bold 6rem SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
margin: 0 0 2rem 0;
padding: 0;
display: block;
-webkit-filter: url(#chromatic-aberration);
filter: url(#chromatic-aberration);
}
.errorPage__statusText {
margin: 0;
padding: 0;
display: block;
font-size: 2rem;
}
.errorPage__message {
margin: 1rem 0;
color: #999;
}
.errorPage__message p {
margin: 0 0 .3rem 0;
padding: 0;
}
.errorPage__actions {
margin-top: 4rem;
display: flex;
justify-content: flex-end;
}
.errorPage__actions>* {
margin-left: 1rem;
}
.btn--primary, .btn--transparent {
border: none;
text-decoration: none;
padding: .5rem 1rem;
}
.btn--transparent {
color: #777;
}
.btn--transparent:hover, .btn--transparent:focus {
color: inherit;
}
.btn--primary {
color: #f4f4f4;
background: #005A9C;
border-radius: 2px;
}
.btn--primary:hover, .btn--primary:focus {
background: #00498B;
}
.gopher {
height: 320px;
align-self: center;
margin-right: 2.5rem;
}
.gopher__image {
height: 100%;
}
</style>
</head>
<body>
<div class="errorPage">
<div class="errorPage__container">
<h1 class="errorPage__statusCode">404!</h1>
<h2 class="errorPage__statusText">Page Not Found</h2>
<div class="errorPage__message">
<p>
Requested page does not exist or was deleted.
</p>
<p>
That's all we know 🤷
</p>
</div>
<div class="errorPage__actions">
<a href="//http.cat/404" class="btn--transparent">Show me cats</a>
<a href="/" class="btn--primary">Go To Home</a>
</div>
</div>
<div class="gopher">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px"
y="0px" width="236.7px" height="320px" viewBox="0 0 438.004 592.145"
enable-background="new 0 0 236.7 320"
class="gopher__image"
xml:space="preserve">
<g>
<path fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round"
stroke-linejoin="round" stroke-miterlimit="10" d="
M45.352,291.011c-13.378,1.876-28.958,5.754-37.066,10.684c-7.337,4.463-6.374,17.6-1.154,24.099
c5.509,6.862,11.974,6.495,19.779,2.905c5.123-2.357,11.293-7.609,17.667-13.492c0.16-5.126,0.339-10.256,0.543-15.375
C45.242,296.793,45.317,293.857,45.352,291.011z"/>

<path fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M429.717,297.195c-7.5-4.56-21.389-8.217-34.018-10.226c0.316,6.816,0.751,14.31,1.339,22.763c0.106,1.527,0.211,3.033,0.313,4.529
c4.959,4.379,9.68,8.068,13.74,9.937c7.807,3.591,14.27,3.958,19.779-2.904C436.091,314.795,437.055,301.656,429.717,297.195z"/>

<path fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M55.17,564.844c-9.854,17.521,15.9,31.951,29.25,19.801c10.908-9.928,19.518-18.348,38.256-21.098
c-14.35-5.906-27-13.281-37.752-21.871C71.807,546.805,61.188,554.146,55.17,564.844z"/>

<path fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M353.745,579.846c11.037,14.283,38.46,1.312,30.443-17.121c-5.267-12.111-15.218-22.156-27.677-28.195
c-8.158,7.618-17.849,14.689-29.308,20.867C339.883,561.402,346.096,569.946,353.745,579.846z"/>
<path fill="none" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M5.048,322.396c3.333-2.397,5.688-4.991,11.784-8.097"/>
<path fill="none" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M432.955,317.896c-3.333-2.397-5.688-4.991-11.783-8.097"/>
<path fill="none" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M60.871,584.57c0.9-4.951,3.6-13.275,11.7-20.476"/>
<path fill="none" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M376.05,582.961c-0.026-5.031-1.304-15.266-8.866-22.195"/>

<path fill="#74cddd" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M41.071,74.477c0-6.969,5.641-12.618,12.6-12.618c4.081,0,7.697,1.951,10,4.964c5.569-9.111,12.283-16.97,19.913-23.729
c-3.78-3.658-9.922-6.33-19.563-7.625c-31.05-4.174-46.917,11.166-50.542,31.725c-3.498,19.838,7.171,41.143,31.004,46.682
c2.46-9.685,5.474-18.569,8.95-26.793C46.585,86.954,41.071,81.365,41.071,74.477z"/>
<path d="M53.671,61.859c-6.959,0-12.6,5.649-12.6,12.618c0,6.888,5.514,12.477,12.362,12.605
c2.479-5.866,5.191-11.399,8.112-16.638c0.687-1.231,1.4-2.434,2.126-3.622C61.368,63.81,57.751,61.859,53.671,61.859z"/>

<path fill="#74cddd" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M413.979,59.094c-3.625-20.56-19.492-35.899-50.541-31.725c-10.765,1.447-17.166,4.61-20.789,8.945
c6.867,5.93,13.257,12.745,19.272,20.518c0.292,0.377,0.57,0.758,0.857,1.136c2.296-3.095,5.963-5.109,10.108-5.109
c6.959,0,12.6,5.649,12.6,12.618c0,6.109-4.335,11.202-10.093,12.366c4.277,8.283,7.745,17.199,10.521,27.142
C407.677,98.434,417.33,78.095,413.979,59.094z"/>
<path d="M385.486,65.477c0-6.969-5.641-12.618-12.6-12.618c-4.146,0-7.812,2.014-10.108,5.109
c4.815,6.345,9.007,12.886,12.615,19.875C381.151,76.679,385.486,71.586,385.486,65.477z"/>

<path fill="#74cddd" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M362.778,57.968c-0.287-0.378-0.565-0.759-0.857-1.136c-6.016-7.773-12.405-14.588-19.272-20.518
c-29.797-25.728-68.582-34.79-124.728-33.123c-48.682,1.447-100.473,9.906-134.337,39.903c-7.63,6.758-14.343,14.617-19.913,23.729
c-0.726,1.188-1.439,2.391-2.126,3.622c-2.921,5.239-5.633,10.771-8.112,16.638c-3.476,8.224-6.49,17.108-8.95,26.793
c-4.767,18.77-7.463,40.533-7.462,66.257c0.002,45.133,8.866,67.528,8.332,110.879c-0.035,2.846-0.11,5.782-0.231,8.821
c-0.204,5.119-0.383,10.249-0.543,15.375c-1.653,53.107-1.062,105.862-1.499,142.036c-0.401,33.204,14.646,62.704,41.845,84.433
c10.752,8.59,23.402,15.965,37.752,21.871c25.113,10.337,55.418,16.186,89.844,16.186c50.265,0,87.456-9.652,114.684-24.336
c11.459-6.178,21.149-13.249,29.308-20.867c20.359-19.008,31.17-41.422,36.009-61.896c11.47-48.523,9.966-84.08,4.831-158.371
c-0.103-1.496-0.207-3.002-0.313-4.529c-0.588-8.453-1.022-15.947-1.339-22.763c-1.733-37.343,0.064-54.317-0.479-96.937
c-0.463-36.271-3.195-63.161-9.306-85.047c-2.776-9.942-6.244-18.858-10.521-27.142C371.785,70.854,367.594,64.312,362.778,57.968z
"/>

<ellipse fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" cx="144.121" cy="91.595" rx="54.9" ry="53.1"/>
<circle cx="115.321" cy="94.294" r="18.9"/>
<circle fill="#FFFFFF" cx="123.421" cy="98.794" r="5.4"/>

<ellipse fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" cx="281.821" cy="85.294" rx="54.9" ry="53.1"/>
<circle cx="253.021" cy="87.995" r="18.9"/>
<circle fill="#FFFFFF" cx="261.121" cy="92.495" r="5.4"/>

<path fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M214.968,170.343c-10.784,0.188-12.401,4.999-21.685,6.657c-0.328,5.727-0.745,13.566-1.163,22.896c-0.9,20.1,24.6,15.6,24.6,3.6
c0-9.129,0-25.714,0-33.132C216.16,170.343,215.583,170.333,214.968,170.343z"/>

<path fill="#FFFFFF" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M216.72,203.495c0,12,25.5,16.5,24.6-3.6c-0.429-9.582-0.857-17.59-1.189-23.353c-11.689-0.533-13.115-5.813-23.411-6.179
C216.72,177.78,216.72,194.365,216.72,203.495z"/>

<path fill="#AC967B" stroke="#000000" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M254.674,150.297c-4.354-4.685-9.521-7.238-16.425-8.471c-3.664,5.366-12.791,9.169-23.479,9.169
c-10.278,0-19.112-3.518-23.034-8.56c-7.391,1.417-12.753,4.185-17.15,9.26c-8.627,9.959-4.437,24.891,7.156,25.695
c4.957,0.344,8.624,0.131,11.541-0.391c9.284-1.658,10.901-6.469,21.685-6.657c0.615-0.01,1.192,0,1.752,0.02
c10.295,0.366,11.721,5.646,23.411,6.179c2.312,0.105,5.024,0.026,8.289-0.316C259.979,175.018,263.645,159.949,254.674,150.297z"
/>
<path d="M214.771,150.995c10.688,0,19.814-3.803,23.479-9.169c1.107-1.622,1.722-3.385,1.722-5.231c0-7.953-11.281-14.4-25.2-14.4
c-13.917,0-25.2,6.447-25.2,14.4c0,2.08,0.778,4.054,2.166,5.84C195.658,147.477,204.493,150.995,214.771,150.995z"/>
</g>
</svg>

</div>
</div>
<svg width="0" height="0">
<filter id="chromatic-aberration">
<feColorMatrix type="matrix" result="red_" values="4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
<feOffset in="red_" dx="2" dy="0" result="red"/>
<feColorMatrix type="matrix" in="SourceGraphic" result="blue_" values="0 0 0 0 0 0 3 0 0 0 0 0 10 0 0 0 0 0 1 0"/>
<feOffset in="blue_" dx="-3" dy="0" result="blue"/>
<feBlend mode="screen" in="red" in2="blue"/>
</filter>
</svg>
</body>
</html>