I have a Project in Flutter Web for which I'd like to create a landing page in pure HTML for SEO purposes.
My intention is to create a Server that serves the static files with higher priority and if the static HTML files would return an error on the request, the built flutter web project should be used as fallback.
I created this server in Golang:
package main
import (
"net/http"
)
func main() {
http.ListenAndServe(":2000", professionalServer{})
}
type professionalServer struct{}
var flutterServer = http.FileServer(http.FileSystem(http.Dir("../../../professional/build/web")))
var staticServer = http.FileServer(http.FileSystem(http.Dir("../../../professional/landing-page/dist")))
func (professionalServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
res := preflightResponseWriter{OutputData: make(map[string]int)}
staticServer.ServeHTTP(res, r)
if res.OutputData["status_code"] == 200 {
staticServer.ServeHTTP(w, r)
} else {
flutterServer.ServeHTTP(w, r)
}
}
type preflightResponseWriter struct {
OutputData map[string]int
}
func (preflightResponseWriter) Header() http.Header {
return http.Header{}
}
func (preflightResponseWriter) Write([]byte) (int, error) {
return 0, nil
}
func (p preflightResponseWriter) WriteHeader(statusCode int) {
p.OutputData["status_code"] = statusCode
}
This would actually work, but the problem is that Flutter Web uses the hash format for routes (i.e. http://website.com/#/dashboard) and browser don't send the part that comes after the hashtag, so my golang server sees http://website.com/, then it checks if the static file server can handle this URL, which it can and so the static file server sends the response.
How can I fix this? Is it possible to send the full URL to the server, including the part that comes after #?
Thanks for your help in advance!
How I solved it inspired by the suggestion I got from #ResamVi:
I followed the steps from the answer so that my app finally has the base href /app/.
Then, in order to make the server work properly, I did these changes to my server file:
package main
import (
"net/http"
"strings"
)
func main() {
http.ListenAndServe(":2000", professionalServer{})
}
type professionalServer struct{}
var flutterServer = http.StripPrefix("/app/", http.FileServer(http.FileSystem(http.Dir("../../../professional/build/web"))))
var staticServer = http.FileServer(http.FileSystem(http.Dir("../../../professional/landing-page/dist")))
func (professionalServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/app") {
rw := preflightResponseWriter{OutputData: make(map[string]int)}
flutterServer.ServeHTTP(rw, r)
if rw.OutputData["status_code"] >= 400 && rw.OutputData["status_code"] < 500 {
http.ServeFile(w, r, "../../../professional/build/web/index.html")
} else {
flutterServer.ServeHTTP(w, r)
}
} else {
staticServer.ServeHTTP(w, r)
}
// check if starts with /app
// if no -> staticServer
// if yes:
// simulate request, check if response code is ok
// if response code is ok, serve via flutterServer.Serve
// else serve file directly
}
type preflightResponseWriter struct {
OutputData map[string]int
}
func (preflightResponseWriter) Header() http.Header {
return http.Header{}
}
func (preflightResponseWriter) Write([]byte) (int, error) {
return 0, nil
}
func (p preflightResponseWriter) WriteHeader(statusCode int) {
p.OutputData["status_code"] = statusCode
}
Now, requests that start with /app will either load an asset for the flutter web app or, if there's no asset fulfilling the request, the index.html will be loaded.
If the request URL doesn't start with /app, the static files are served.
This may be more suitable as a comment where I'd ask clarify if you want to change the web app's URL strategy but I don't have the rep for that (forgive me lord, but this rule is a bit restrictive)
If you are free to change the URL strategy you can do so by adding the dependency at the appropriate place as described in the link above:
import 'package:url_strategy/url_strategy.dart';
void main() {
setPathUrlStrategy();
runApp(MyApp());
}
Refer also to: How to remove hash (#) from URL in Flutter web
Related
I'm writing a simple rest api server and I cannot get route dynamic url using net/http
http://localhost:8080/book/name
where name can be any string.
this was my attempt:
func viewIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, 'html')
}
http.HandleFunc("/book/{name}", view)
this does not work, there is some cryptic note in the documentation of HandleFunc:
The documentation for ServeMux explains how patterns are matched.
This is a working solution.
The only interesting part it that patterns ending with / will be treated as prefix matcher for all urls.
So pattern /book/ will match:
/book/
/book/a
/book/bb
/book/a/b
sample simplified code:
package main
import (
"fmt"
"strings"
"net/http"
)
func book(name string) {
fmt.Printf("requesting book '%s'\n",name);
}
func view(w http.ResponseWriter, r *http.Request) {
url := r.URL.String()
if strings.HasPrefix(url, "/book/"){
name := url[6:]
book(name)
fmt.Fprint(w, name)
}
}
func main() {
http.HandleFunc("/book/", view)
http.ListenAndServe("localhost:8080", nil)
}
I want to serve some static files (.js, .css, ...) from my hyper server.
Currently the only way I can think of is inlining the files as strings / load them on startup.
Is there a better way to directly serve an entire directory or selected files?
After typing in the words "hyper static" into crates.io, the first result was hyper-staticfile. The project's GitHub repository has an examples directory, with one such example:
extern crate futures;
extern crate hyper;
extern crate hyper_staticfile;
extern crate tokio_core;
// This example serves the docs from target/doc/hyper_staticfile at /doc/
//
// Run `cargo doc && cargo run --example doc_server`, then
// point your browser to http://localhost:3000/
use futures::{Future, Stream, future};
use hyper::Error;
use hyper::server::{Http, Request, Response, Service};
use hyper_staticfile::Static;
use std::path::Path;
use tokio_core::reactor::{Core, Handle};
use tokio_core::net::TcpListener;
type ResponseFuture = Box<Future<Item=Response, Error=Error>>;
struct MainService {
static_: Static,
}
impl MainService {
fn new(handle: &Handle) -> MainService {
MainService {
static_: Static::new(handle, Path::new("target/doc/")),
}
}
}
impl Service for MainService {
type Request = Request;
type Response = Response;
type Error = Error;
type Future = ResponseFuture;
fn call(&self, req: Request) -> Self::Future {
if req.path() == "/" {
let res = Response::new()
.with_status(hyper::StatusCode::MovedPermanently)
.with_header(hyper::header::Location::new("/hyper_staticfile/"));
Box::new(future::ok(res))
} else {
self.static_.call(req)
}
}
}
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let addr = "127.0.0.1:3000".parse().unwrap();
let listener = TcpListener::bind(&addr, &handle).unwrap();
let http = Http::new();
let server = listener.incoming().for_each(|(sock, addr)| {
let s = MainService::new(&handle);
http.bind_connection(&handle, sock, addr, s);
Ok(())
});
println!("Doc server running on http://localhost:3000/");
core.run(server).unwrap();
}
I want to close an mgo session after it' fully streamed to the client. At first, I thought this might work, but it seems the defer just waits until the the func begins to return or something.
func (c App) OpenFile(fileId string) revel.Result {
// convert string to bson.ObjectId
objId := bson.ObjectIdHex(fileId)
file, session := OpenFile(objId)
defer session.Close()
// memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time
var now = time.Now()
return c.RenderBinary(file, "filename", "inline", now)
}
I have no idea what your OpenFile function doing but I assume it using Mongo's GridFS to access the file. You are right defer session.Close() is called right before the return. If you use file Revel closes it automatically but in your case, you also want to close the session.
The only one way around it is to make custom Reader that can be used by Revel in RenderBinary. Something like:
package main
import (
"io"
"os"
"gopkg.in/mgo.v2"
)
type MongoReader struct {
io.Reader
File *os.File
Session *mgo.Session
}
func (mr *MongoReader) Read(p []byte) (n int, e error) {
return mr.File.Read(p)
}
func (mr *MongoReader) Close() {
mr.File.Close()
mr.Session.Close()
}
func main() {
mr := MongoReader{}
mr.Session, mr.File = OpenFile("abababa")
return c.RenderBinary(mr, "filename", "inline", now)
}
I didn't test it on Revel but as code is quite simple I think it should work. Maybe you will need some os.File specific methods.
I have a global variable that I am trying to use across two different functions, and unable to figure out why the following code is not working...
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net"
"net/http"
)
type Message struct {
Body string
}
var api rest.Api
func hostLookup(w rest.ResponseWriter, req *rest.Request) {
ip, err := net.LookupIP(req.PathParam("host"))
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&ip)
}
func foo() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
&rest.Route{"GET", "/lookup/#host", hostLookup},
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
}
func bar() {
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
func main() {
foo()
bar()
}
The above code does not work... the HTTP server does not route the request to the hostLookup function.
However - if I move the following line from bar()
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
to the end of function foo(), then it works correctly
What am I doing wrong?
Your problem is two fold...
For one, you declare
var api rest.Api
but the rest.New() returns a *rest.Api
func NewApi() *Api {
Secondly, in your foo() function, you are creating a local variable called api instead of using your package variable.
Instead of
api := rest.NewApi()
It should be
api = rest.NewApi()
So, the fix is to add a * before rest.Api as in var api *rest.Api and remove a colon from the setting of api as in api = rest.NewApi()
I am having issues getting the Gorilla Mux library for Go to work. From the documentation I have read and all the debugging I've done, I cannot seem to figure out what the problem is. Here's what I've got for routing:
Folder structure:
project_root
|-- main.go
|-- routes
|-- routes.go
|-- user.go
main.go:
package main
import (
"fmt"
"net/http"
"./routes"
)
func main() {
r := routes.CreateRoutes(http.Dir("./content"))
http.Handle("/", r)
err := http.ListenAndServe(fmt.Sprintf("%s:%d", "localhost", 8000), nil)
if err != nil {
fmt.Println("Error: ", err)
}
}
routes/routes.go
package routes
import (
"net/http"
"github.com/gorilla/mux"
)
func CreateRoutes(staticDir http.FileSystem) *mux.Router {
r := mux.NewRouter()
// Serve static pages (i.e. web app)
r.PathPrefix("/").Handler(http.FileServer(staticDir))
// Serve User Pages
createUserRoutes(r)
return r
}
routes/user.go
package routes
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func createUserRoutes(r *mux.Router) {
user := r.PathPrefix("/user/").Subrouter()
// Create a new user
user.Path("/new").Methods("PUT").HandlerFunc(newUserHandler)
// Remove a user
user.Path("/remove/{username:[a-z][a-z0-9]+}").Methods("DELETE").HandlerFunc(removeUserHandler)
// Update a user
user.Path("update/{username:[a-z][a-z0-9]+").Methods("POST").HandlerFunc(updateUserHandler)
// Get a user (Get user information)
user.Path("/{username:[a-z][a-z0-9]+").Methods("GET").HandlerFunc(getUserHandler)
}
func newUserHandler(resp http.ResponseWriter, req *http.Request) {
// Do something that might cause an error
if err != nil {
fmt.Println(err)
resp.WriteHeader(409)
resp.Write([]byte(err.Error()))
} else {
fmt.Println("Created new user")
resp.WriteHeader(201)
resp.Write([]byte("Created new user"))
}
}
func removeUserHandler(resp http.ResponseWriter, req *http.Request) {
}
func updateUserHandler(resp http.ResponseWriter, req *http.Request) {
}
func getUserHandler(resp http.ResponseWriter, req *http.Request) {
}
Whenever I make a request to root path of the server (i.e. the path that serves the static content), the server responds as intended, with the main page. However, any other calls result in a 404 response (I test requests using cURL). For example, a malformed request to http://localhost:8000/user/new should return a 409, but instead returns a 404. Same if I expect a 201 response.
Everything looks right and I've triple checked it, but I cannot figure out what the issue here is.
Turns out the solution was simple (like it usually is). This line in routes.go
r.PathPrefix("/").Handler(http.FileServer(staticDir))
was causing the unintended routing. When PathPrefix is used, it seems to route all URLs to the first matching prefix (in this case this prefix). This explains why static files were being served, but nothing else works.
The fix is to use the Path function instead. There's a subtle difference as explained in the docs; PathPrefix "matches if the given template is a prefix of the full URL path", whereas Path does not. Hence the line above now looks like this to solve the issue I was having:
r.Path("/").Handler(http.FileServer(staticDir))