I am trying to serve static files with Hunchentoot, from the www directory inside my project. My acceptor is defined as:
(defvar *acceptor* (make-instance 'easy-acceptor
:port 4242
:document-root (truename "./www/")))
I then start it with:
(start *acceptor*)
The acceptor works, in that I can use define-easy-handler to create a root page:
(define-easy-handler (index :uri "/") ()
(with-html-output-to-string (_)
(:html
(:head
(:title "Hello world")
(:body
(:h1 "Hello world!))))))
... and when I browse to http://localhost:4242/ I see that page.
But no static files are served from my www directory. E.g. if I create www/jquery-3.2.1.min.js and browse to http://localhost:4242/jquery-3.2.1.min.js I receive a 404.
127.0.0.1 - [2017-08-11 08:08:02] "GET /jquery-3.2.1.min.js HTTP/1.1" 404 355 "-" "Mozilla/5.0 (X11; FreeBSD amd64; rv:54.0) Gecko/20100101 Firefox/54.0"
HELLOWORLD> (directory (make-pathname :directory '(:relative "www") :name :wild :type "js"))
(#P"/usr/home/duncan/code/helloworld/www/jquery-3.2.1.min.js")
You must ensure that the directory and the files in it have proper permissions set. The directory needs to have the execute x permission to allow the server program to access the contents of the directory, and files need at least the read r permission.
Related
My application runs an HTTP server that serves some static files. Most files are reachable under /static/ but some, like index.html, must be reachable at the root.
This code tries to implement that by embedding the files in an embed.FS (for demonstration, I'm only embedding index.html here):
package main
import (
"net/http"
"embed"
"io/fs"
"log"
)
//go:embed index.html
var files embed.FS
type primaryFiles struct {}
func (pf *primaryFiles) Open(name string) (fs.File, error) {
// name will be "." for paths / and /index.html, I guess that's a feature
if name == "." {
return files.Open("index.html")
}
return nil, fs.ErrNotExist
}
func main() {
http.Handle("/", http.FileServer(http.FS(&primaryFiles{})))
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(files))))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Now when running the code, I can query the index.html just fine at both http://localhost:8080/static/ and http://localhost:8080/static/index.html. However, at http://localhost:8080/ and http://localhost:8080/index.html, the browser will give me ERR_TOO_MANY_REDIRECTS. Why is that happening? How can I fix it?
I already tried to hand through the ".", which yields a file listing instead of the index.html content. I'm on go version go1.17.3 darwin/arm64. I also tried to figure out what's happening with curl:
$ curl -v http://localhost:8080/index.html
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Location: ./
< Date: Mon, 06 Dec 2021 22:05:50 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
$ curl -v http://localhost:8080/
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Location: ..//
< Date: Mon, 06 Dec 2021 22:05:12 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
This doesn't help me understand what's happening – okay. /index.html is redirected to ./, that seems to make sense. But / being redirected to ..// … I don't know what to make of that.
Your primaryFiles.Open implementation, when given ".", returns a file rather than a dir. This is an error.
The documentation on fs.FS.Open should lead you to fs.ValidPath whose godoc states the following.
package fs // import "io/fs"
func ValidPath(name string) bool
ValidPath reports whether the given path name is valid for use in a call to
Open.
Path names passed to open are UTF-8-encoded, unrooted, slash-separated
sequences of path elements, like “x/y/z”. Path names must not contain an
element that is “.” or “..” or the empty string, except for the special case
that the root directory is named “.”. Paths must not start or end with a
slash: “/x” and “x/” are invalid.
Note that paths are slash-separated on all systems, even Windows. Paths
containing other characters such as backslash and colon are accepted as
valid, but those characters must never be interpreted by an FS
implementation as path element separators.
net/http.FileServer is banking on the fact that recursively redirecting on ../ should eventually get to some directory, but there is no directory to be found based on the way your primaryFiles.Open works. It could be argued that this is an opportunity to enhance net/http.FileServer, but it's not clear.
What's happening is partly documented in the http package:
As a special case, the returned file server redirects any request ending in "/index.html" to the same path, without the final "index.html".
So any request to index.html is not seen in my Open fn, since it redirects to ..
What the documentation does not tell that a request to . seems to be handled as follows:
the directory for . is queried via Open. This should return a directory, which my original code did not and caused the error.
if a directory is returned, it is searched for a file index.html and if one exists, another request to Open is made. This is what I missed.
So to fix the code, I need to pipe through the request to both . and index.html to the actual files:
func (pf *primaryFiles) Open(name string) (fs.File, error) {
if name == "." || name == "index.html" {
return files.Open(name)
}
return nil, fs.ErrNotExist
}
I've set up a hunchentoot server with ssl. I'd like regular http requests to be redirected to https.
It seems like some combination of hunchentoot:define-easy-handler and hunchentoot:redirect is the way to go, but I can't figure it out.
Here's what I have so far:
(defvar *https-handler*
(make-instance 'hunchentoot:easy-ssl-acceptor
:name 'ssl
:ssl-privatekey-file #P"/path/to/privkey.pem"
:ssl-certificate-file #P"/path/to/cert.pem"
:port 443))
(hunchentoot:start *https-handler*)
Yes, you can add simple http handler with redirect to ssl version:
(defvar *http-handler*
(make-instance 'hunchentoot:easy-acceptor
:name 'http
:port 80))
(hunchentoot:define-easy-handler (redir-to-ssl :uri (lambda (uri) t) :acceptor-names '(http)) ()
(hunchentoot:redirect "/" :protocol :https)) ; where magic happens
...and then start it too:
(hunchentoot:start *http-handler*)
This version redirects simply to index /.
Well, I am using the hunchentoot:*dispatch-table* directly. The way to redirect it independent of the path I discovered was to hunchentoot:redirect unless (hunchentoot:ssl-p) inside the handler. Most of my defuned handlers are wrapped inside a macro for authenthentication. So, I merely had to modify that macro, and then M-x slime-who-macroexpands -> C-c C-k.
(unless (hunchentoot:ssl-p)
(hunchentoot:redirect (hunchentoot:request-uri*)
:protocol :https))
If you need to indiscriminately redirect every HTTP request to HTTPS, using easy-acceptor is unnecessary. I'd suggest to define a specialized acceptor:
(defclass http-to-https-acceptor (hunchentoot:acceptor) ())
(defmethod hunchentoot:acceptor-dispatch-request ((acceptor http-to-https-acceptor) request)
(hunchentoot:redirect (hunchentoot:request-uri request)
:protocol :https))
Then at some point:
(hunchentoot:start (make-instance 'http-to-https-acceptor :port 80))
I tried to get an SSL version of hunchentoot running, on clozure common lisp 1.11.1 running on a MacBook Pro with OS 10.13.6
I started by creating certificates in the /tmp directory, as follows:
openssl req -new -x509 -nodes -out server.crt -keyout server.key
Here is my lisp code:
(ql:quickload "hunchentoot")
(in-package hunchentoot)
(define-easy-handler (test-ssl :uri "/secure") ()
(setf (content-type*) "text/plain")
"SECURED PAGE")
(defvar *ssl-acceptor*
(make-instance 'easy-ssl-acceptor
:port 7777
:ssl-privatekey-file #P"/tmp/server.key"
:ssl-certificate-file #P"/tmp/server.crt"))
(start *ssl-acceptor*)
When I try to access https://localhost:7777/secure from my browser, nothing shows up on the browser, and hunchentoot throws the following error to the log window:
Unhandled execption 11, Exception occurred while executing foreign code
Any suggestions for how to fix this?
I have defined a simple system in helloworld.asd:
(asdf:defsystem #:helloworld
:description "helloworld"
:author "Duncan Bayne <duncan#bayne.id.au>"
:license "WTFNMF"
:depends-on (#:hunchentoot)
:serial t
:components ((:file "package")
(:file "helloworld")))
... a package definition in package.lisp:
(defpackage #:helloworld
(:use #:cl #:hunchentoot))
... and a corresponding hello world webserver in helloworld.lisp:
(in-package #:helloworld)
(defvar *acceptor* (make-instance 'acceptor :port 4242))
(start *acceptor*)
(define-easy-handler (greet :uri "/hello") ()
"<html><body><h1>Hello World!</h1></body></html>")
In the SLIME REPL I start the web server with:
CL-USER> (load "/usr/home/duncan/code/helloworld/helloworld.asd")
CL-USER> (ql:quickload "helloworld")
If I navigate to http://localhost:4242/hello, I'd expect to see my hello world HTML. Instead I get a 404 error, and the log shows:
127.0.0.1 - [2017-08-10 08:18:19] "GET /hello HTTP/1.1" 404 341 "-" "Mozilla/5.0 (X11; FreeBSD amd64; rv:54.0) Gecko/20100101 Firefox/54.0"
I suspect I'm missing something fairly obvious here; any tips / pointers to documentation would be appreciated. System details are:
Clozure Common Lisp Version 1.11 (FreebsdX8664)
FreeBSD 11.1-RELEASE amd64
Hunchentoot 1.2.37
Mozilla Firefox 54.0.1
SLIME 20170804.1113
You are making an instance of ACCEPTOR instead of EASY-ACCEPTOR (or a subclass). The easy handler is registered but your acceptor is not going to use it. This should work, for example:
(defvar *acceptor* (make-instance 'easy-acceptor :port 4242))
(start *acceptor*)
(define-easy-handler (test :uri "/test") () "Pass")
I am trying to send a string via http/post in Racket, this is what I tried so far after reading the Racket HTTP Client Documentation
#lang racket
(require net/http-client)
(define
myUrl "https://something.com")
(http-conn-send!
(http-conn-open
myUrl
#:ssl? #t)
#:version "1.1"
#:method "POST"
#:data "Hello")
But with this I receive the following error:
tcp-connect: connection failed
detail: host not found
address: https://www.w3.org/
port number: 443
step: 1
system error: nodename nor servname provided, or not known; errno=8
I tried it with several different adresses.
I am new to racket and programming in general and unable to figure out what I am missing.
In your example, the hostname is only the www.w3.org portion -- not including the scheme (http or https) nor any path. So for example this does work:
(http-conn-open "www.w3.com"
#:ssl? #t)
To make a post request, you could do this:
#lang racket
(require net/http-client)
(define-values (status headers in)
(http-sendrecv "www.w3.com"
"/"
#:ssl? #t
#:version "1.1"
#:method "POST"
#:data "Hello"))
(displayln status)
(displayln headers)
(displayln (port->string in))
(close-input-port in)
In Racket, a function can return multiple values. http-sendrecv returns three, and the define-values assigns each one to a variable.
net/http-client provides other functions to let you make a connection to a host, make multiple requests on that connection, then close the connection.