如何在Go中的os.Stdin和http上选择?

Say I want to accept an animal. The user can either set the type of animal at the What type of animal? prompt on a terminal, or she can go to http://localhost:1234/animal?type=kitten

Whichever she does, the terminal will read What type of animal? kitten (assuming she chose a kitten) and the program will then prompt the user on the terminal (and only the terminal) What is the kitten's name?

My thought was to use channels to go routines, but since both go routines will be stuck in a function call (Scan() for the terminal, ListenAndServe() for http) then I'm not clear how to stop the go routine that is still in a function call once the input is received. The normal method of selecting on channels won't work.

In C I'd just do a select(2) on the two relevant file descriptors (fd 0 and the fd from listen()) but I don't think that's how to do it in Go.

Note: I answered this down below. If you're down voting the question this I'd be curious to know why as I would have found the answer I came up with useful. If you're down voting the answer, I'd really be interested in knowing why. If it's non-idiomatic Go or if it has other issues I'm missing, I'd really like to fix them.

OK, I figured out a solution for what I was trying to do.

This might not be the most Go idiomatic way though. If the channels took a struct with input source and the string I could probably toss the select and the webinput channel.

package main

import (
    "bufio"
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func getHTTPAnimal(input chan string) *http.Server {
    srv := &http.Server{Addr: ":1234"}

    http.HandleFunc("/animal", func(w http.ResponseWriter, r *http.Request) {
        animal, ok := r.URL.Query()["type"]
        if !ok || len(animal[0]) <= 0 {
            io.WriteString(w, "Animal not understood.
")
            return
        }
        io.WriteString(w, "Animal understood.
")
        input <- string(animal[0])
    })

    go func() {
        if err := srv.ListenAndServe(); err != nil {
            if err.Error() != "http: Server closed" {
                fmt.Printf("getHTTPAnimal error with ListenAndServe: %s", err)
            }
        }
    }()

    return srv
}

func getTerminalInput(input chan string) {
    scanner := bufio.NewScanner(os.Stdin)
    for {
        scanner.Scan()
        input <- scanner.Text()
    }
}

func main() {
    input := make(chan string)
    webinput := make(chan string)

    srv := getHTTPAnimal(webinput)
    fmt.Print("Enter animal type: ")
    go getTerminalInput(input)

    var animal string
    select {
    case ta := <-input:
        animal = ta
    case webta := <-webinput:
        animal = webta
        fmt.Printf("%s
", animal)
    }

    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    srv.Shutdown(ctx)
    close(webinput)

    fmt.Printf("Enter animal name: ")
    name := <-input
    fmt.Printf("Congrats on getting %s the half-%s
", name, animal)
}

You can stop goroutine with close statement.

Example:

package main

import "sync"
func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    ch := make(chan int)
    go func() {
        for {
            foo, ok := <- ch
            if !ok {
                println("done")
                wg.Done()
                return
            }
            println(foo)
        }
    }()
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    wg.Wait()
}

There is couple of ways to stop goroutine:

1) by closing the read channel:

msgCh := make(chan string)

go func(){
    for msg, ok := range msgCh {
        if !ok {
            return
        }
    }
}()

msgCh <- "String"
close(msgCh)

2) passing some sort of kill switch

msgCh := make(chan string)
killSwitch := make(chan struct{})

go func(){
    for {
        select{
        case msg <- msgCh:
            log.Println(msg)
        case <-killSwitch:
            return
        }
    }
}
msgCh <- "String"
close(killSwitch)

With approach NR2 you are avoiding writing to closed chan that would end up as panic. Also close signal is populated as fanout pattern (all places where that chan is used will receive that signal)