Skip to content

Instantly share code, notes, and snippets.

@icub3d
Created January 17, 2013 07:29
Show Gist options
  • Save icub3d/4554320 to your computer and use it in GitHub Desktop.
Save icub3d/4554320 to your computer and use it in GitHub Desktop.
A complete example of creating a service in go using RPC.
// greeter/config.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"sync"
)
// This is the config file we read from. Our flags from the command
// line will set this appropriately.
var ConfigFile string
// This is the system configuration.
var Config map[string]string
// This is the lock we'll use for the config when we are updating it.
var ConfigLock sync.RWMutex
// Reads the contents of ConfigFile and makes a map[string]string out
// of it which is saved to Config. The contents of the file should be
// a json object with both they keys and values as strings.
func ReadConfig() error {
// Read the contents of the config file.
bytes, err := ioutil.ReadFile(ConfigFile)
if err != nil {
return fmt.Errorf("reading config file: %v", err)
}
// Lock the config so we can change it.
ConfigLock.Lock()
defer ConfigLock.Unlock()
// Change the config.
Config = make(map[string]string)
err = json.Unmarshal(bytes, &Config)
if err != nil {
return fmt.Errorf("unmarshalling config: %v", err)
}
return nil
}
{
"greeting": "Howdy Doodie!"
}
// greeter/flags.go
package main
import (
"flag"
)
func init() {
// Prepare the flags to be read from the command line.
flag.StringVar(&ConfigFile, "config", "/tmp/config.json",
"The location of the configuration file.")
}
# /etc/systemd/system/greeter.service
[Unit]
Description=A simple greeting service
After=network.target
[Service]
ExecStart=/home/jmarsh/go/bin/greeter
ExecStop=/home/jmarsh/go/bin/servicer stop
ExecReload=/home/jmarsh/go/bin/servicer reload
// greeter/main.go
package main
import (
"fmt"
"flag"
"net"
"net/http"
)
// The following is the handler for the request "/".
func printGreeting(w http.ResponseWriter, r *http.Request) {
if greeting, ok := Config["greeting"]; ok {
fmt.Fprintln(w, greeting)
} else {
fmt.Fprintln(w, "Hello, default greeting!")
}
}
func main() {
flag.Parse()
// Load the initial configuration.
err := ReadConfig()
if err != nil {
fmt.Println("reading config:", err)
return
}
// Start the listener
listener, err := net.Listen("tcp", "127.0.0.1:9000")
if err != nil {
fmt.Println("opening listening socket:", err)
return
}
// Initialize the RPC Interface.
err = StartRPC(listener)
if err != nil {
fmt.Println("StartRPC:", err)
return
}
// Create your web app as normal.
http.HandleFunc("/", printGreeting)
// Start listening http request. If I were using fastcgi, I'd simply
// do the same but with fcgi.Serve.
err = http.Serve(listener, nil)
if err != nil {
fmt.Println("serve:", err)
}
}
// greeter/rpc.go
package main
import (
"fmt"
"log"
"net"
"net/http"
"net/rpc"
)
// This is our RPC object we'll use to do things to the http service.
type ServiceHandler struct {
// The listener we'll close with when stopping.
listener net.Listener
}
// This singles the server to stop if it's running. incoming ars are
// nil here because we don't use them and the replay is simply a
// boolean to signify success or failure.
func (s *ServiceHandler) Stop(args int, reply *bool) error {
// We should only stop it if we have one.
if s.listener != nil {
log.Println("Stopping listener")
s.listener.Close()
*reply = true
return nil
}
*reply = false
return fmt.Errorf("listener is not open")
}
// This signals the server to reload it's config.
func (r *ServiceHandler) Reload(args int, reply *bool) error {
err := ReadConfig()
if err != nil {
*reply = false
return err
}
*reply = true
return nil
}
// Initialize the RPC service and start listening for connections.
func StartRPC(listener net.Listener) error {
// Create the ServiceHandler, register it, and set it to be handled.
rpcsh := &ServiceHandler{listener: listener}
rpc.Register(rpcsh)
rpc.HandleHTTP()
l, err := net.Listen("tcp", ":9001")
if err != nil {
return fmt.Errorf("opening rpc listener: %v", err)
}
// It seems a bit unituitive to me, but this is how we start
// listening (i.e. using the http package).
go http.Serve(l, nil)
return nil
}
// servicer/servicer.go
package main
import (
"flag"
"fmt"
"net/rpc"
"os"
)
func main() {
// Get the action the user wants us to perform
flag.Parse()
action := flag.Arg(0)
// Translate the action into an RPC method.
method := ""
switch action {
case "stop":
method = "ServiceHandler.Stop"
case "reload":
method = "ServiceHandler.Reload"
}
if method == "" {
fmt.Println("unknown action:", action)
os.Exit(1)
}
// Connect to the server.
client, err := rpc.DialHTTP("tcp", ":9001")
if err != nil {
fmt.Println("dialing:", err)
os.Exit(1)
}
defer client.Close()
// Issue the method
var reply bool
err = client.Call(method, 1, &reply)
if err != nil {
fmt.Println("calling:", err)
os.Exit(1)
}
// Send the proper exit code to the system. How you respond to your
// service manager may differ slightly.
if !reply {
os.Exit(1)
}
}
$ cd servicer
$ go install
$ cd ../greeter
$ go install
$ cd ..
$ sudo cp greeter.services /etc/systemd/system
$ cp config.json /tmp
$ sudo systemctl start greeter
$ curl http://localhost:9000
Howdy Doodie!
$ emacs /tmp/config.json
$ sudo systemctl reload greeter
$ curl http://localhost:9000
Hey there, partner!
$ sudo systemctl stop greeter
$ curl http://localhost:9000
curl: (7) couldn't connect to host at localhost:9000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment