Using Caddy as library for go package?

I have started learning web development and decided to learn Go to handle the backend. Having come across Caddy has me pretty excited by I seem to be hitting a wall in terms of what its been designed to do. Part of this is definitely due to my lack of understanding of the Go nomenclature.

I have two or three projects on the go on a digitalOcean VPS, and the VPS has a domain redirected to it. So in order to switch between projects I need to kill the Go server and switch working directories and run the server again. The server is using gorilla/mux, gorilla/handlers, and net/http.

From what I understand about Caddy, I should be able to run all three projects from the same VPS from three different domains and handle all requests through Caddy while still accessing the Go functions that handle the requests on my current server configuration.

Currently the server.go looks something like this:

package server

import (
    "github.com/schmorrison/website/routes/callback"
    "github.com/schmorrison/website/routes/home"
    "github.com/schmorrison/website/routes/middleware"
    "github.com/schmorrison/website/routes/user"
    "github.com/schmorrison/website/routes/logout"
    "github.com/codegangsta/negroni"
    "github.com/gorilla/mux"
    "github.com/gorilla/handlers"
    "net/http"
    "log"
    "os"
)

const (
    PRIV_KEY   = "privkey.pem"
    CERT = "cert.pem"
)

func StartServer() {
    r := mux.NewRouter()

    r.HandleFunc("/", home.HomeHandler)
    r.HandleFunc("/callback", callback.CallbackHandler)
    r.Handle("/user", negroni.New(
        negroni.HandlerFunc(middlewares.IsAuthenticated),
        negroni.Wrap(http.HandlerFunc(user.UserHandler)),
    ))
    r.HandleFunc("/logout", logout.LogoutHandler)
    r.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir("public/"))))
    err := http.ListenAndServeTLS(":443", CERT, PRIV_KEY, handlers.LoggingHandler(os.Stdout, r))
    log.Fatal(err)

What I can’t figure out is do I write my Go functions and handlers into Caddy or do I import Caddy into my server package?

I have been going over the documentation and the github wiki but like I said the nomenclature hasn’t stuck yet.

Thanks for the help,
Schmorrison

Hey @schmorrison - just FYI I’ve edited your post for formatting; you can use three backticks to form a code block. , it makes it more readable. :slight_smile:

What is it exactly that you would like Caddy to do for you?

Essentially what I want is to respond to requests for one of three domains and handle those requests with go functions.

If that’s all you need, then it might just be easiest to write an HTTP handler in Go that checks req.Host, and depending on what it is, execute the middleware (handler) for that hostname.

Using Caddy as a library is a little higher-level: you pass in a Caddyfile to caddy.Start(), but if all you need is those 3 hosts then you’re already 90% of the way there with your Go code above.

If you wouldnt mind indulging me with some info or places to look. Even just a repo I can look at. I do intend to be working toward that higher level even if it is unapplicable in certain cases.

Can’t think of an example off the top of my head, but in a ServeHTTP handler:

switch req.Host {
case "site1.com":
    site1handler.ServeHTTP(w, r)
case "site2.com":
    site2handler.ServeHTTP(w, r)
case "site3.com":
    site3handler.ServeHTTP(w, r)
}

Something like that should be all you need. :slight_smile: I don’t think you’ll need Caddy for it.

I believe gorilla mux can also route based on host.

I was just thinking Caddy because I’ll have a half-dozen or more domains and handling all those certs gets complicated. Figured I might be able to use Caddy to avoid reinventing the wheel.

Well, Caddy isn’t meant to be used just as a certificate manager, but what you can use it for is reverse-proxying to your Go application. In other words, put it in front of your Go services and serve your Go apps on some localhost ports and have Caddy reverse proxy to them.

Sorry I guess I misinterpreted the homepage title.

Caddy - The HTTP/2 Web Server with Fully Managed SSL

What you read is accurate: Caddy is a web server. It does fully manage the TLS assets (I use the term “SSL” on the homepage because that’s what people are searching for, unfortunately) that it needs. What you don’t want to do is use Caddy only as a means to obtain and keep certificates renewed; there are other tools for that. Caddy’s implementation of its magic TLS feature are very specific to serving its own. Although it does place the certs on disk for other processes and programs to use, I don’t recommend it, you will probably find that it gets hairy pretty quickly.

Anyway, I recommend plopping Caddy in front of your application and reverse proxying to your Go apps. That way, you get all the benefits of Caddy (easy to configure, managed TLS, etc) without having to rewrite your Go apps.

If you want to embed that into the same process as your Go services, you can use caddy.Start() and pass it a Caddyfile as if you were running Caddy separately. It’s essentially the same thing, but without an extra process to manage.

Great explanation! Thankyou!

I didn’t really intend to use it as a credential manager, but it was certainly a nice feature.

In any case I’ll likely try to figure out exactly what a reverse proxy does!!

You will probably want to see the proxy docs. :wink:

So if I am understanding correctly I write a Caddyfile that looks something like this:

mysite.com {
    root /www/mysite.com
    gzip
    log ../access.log
    proxy / localhost:1234
    }

mysite2.com {
    root /www/mysite2.com
    gzip
    log ../access2.log
    proxy / localhost:2345 
    }

And write my respective go apps to listenandserve on those localhost:ports configurations.

1 Like

Nevermind. I’m glad you sorted me out because this is quite amazing work.

Thanks a million!
Schmorrison

1 Like

You got it! That should be what you need.

With that Caddyfile, you can start the caddy command as a separate program or you can call caddy.Start() from your own Go program, so you won’t need a separate binary in that case. Your choice!

I understand the idea of using caddy as a proxy to a traditional Go net/http server.

Is there a way to hook directly my HandleFuncs directly into Caddy itself, bypassing the proxy method?

@willie Here is the guide for chaining in your own middleware: https://github.com/mholt/caddy/wiki/Writing-a-Plugin:-HTTP-Middleware

1 Like

Thanks for the help. Sorry to ask more questions, but I’m still having trouble working with a simple middleware example. Given this code:

package main

import (
	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/mholt/caddy/caddytls"

	_ "github.com/mholt/caddy/caddyhttp"

	"flag"
	"log"
	"net/http"
	"os"
)

type MyHandler struct {
	Next httpserver.Handler
}

func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	log.Println("serve")

	w.Write([]byte("Hello, I'm a caddy middleware"))
	//return 200, nil
	return h.Next.ServeHTTP(w, r)
}

func setup(c *caddy.Controller) error {
	log.Println("setup")

	httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
		return MyHandler{Next: next}
	})

	return nil
}

func init() {
	log.Println("init")

	caddy.RegisterPlugin("middle", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

func main() {
	flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v01.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory")
	flag.Parse()

	newPath, err := caddytls.StorageFor(caddytls.DefaultCAUrl)
	log.Println(newPath)

	caddy.AppName = "Sprocket"
	caddy.AppVersion = "1.2.3"

	log.Println(caddy.DefaultConfigFile)

	f, err := os.Open("Caddyfile")
	caddyfile, err := caddy.CaddyfileFromPipe(f)
	//	caddyfile, err := caddy.LoadCaddyfile("http")
	if err != nil {
		log.Fatal(err)
	}

	log.Println("path:", caddyfile.Path())
	log.Println("serverType:", caddyfile.ServerType())
	log.Println("caddyfile content:", string(caddyfile.Body()))

	instance, err := caddy.Start(caddyfile)
	if err != nil {
		log.Fatal(err)
	}

	instance.Wait()
}

And a Caddyfile:

localhost:8080
middle

I get the following error:

Caddyfile:2 - Parse error: Unknown directive 'middle'

I know that the init function gets called, but setup does not. Any ideas?

You probably forgot to put your directive in the list in order; without being in that list, the http server doesn’t know in which order to execute your directive.