Статьи

Использование Go для создания службы REST поверх mongoDB

Я следил за го (go-lang) некоторое время и наконец у меня было время немного поэкспериментировать с ним. В этой статье мы создадим простой HTTP-сервер, который использует mongoDB в качестве бэкэнда и предоставляет очень простой REST API.

В оставшейся части этой статьи я предполагаю, что вы настроили и работаете в среде go. Если нет, обратитесь на веб-сайт go-lang за инструкциями ( https://golang.org/doc/install ).
Прежде чем мы начнем, мы должны получить драйверы Монго на ходу. В консоли просто введите следующее:

1
go get gopkg.in/mgo.v2

Это установит необходимые библиотеки, чтобы мы могли получить доступ к mongoDB из нашего кода go.

Нам также нужны данные для экспериментов. Мы будем использовать ту же настройку, что и в моей предыдущей статье ( http://www.smartjava.org/content/building-rest-service-scala-akka-http-a… ).

Загрузка данных в MongoDB

Мы используем некоторую информацию об акциях, которую вы можете скачать здесь ( http://jsonstudio.com/wp-content/uploads/2014/02/stocks.zip ). Вы можете легко сделать это, выполнив следующие шаги:

Сначала получите данные:

1
wget http://jsonstudio.com/wp-content/uploads/2014/02/stocks.zip

Запустите mongodb в другом терминале

1
mongod --dbpath ./data/

И, наконец, используйте mongoimport для импорта данных.

1
unzip -c stocks.zip | mongoimport --db akka --collection stocks --jsonArray

И в качестве быстрой проверки запустите запрос, чтобы увидеть, все ли работает:

01
02
03
04
05
06
07
08
09
10
11
jos@Joss-MacBook-Pro.local:~$ mongo akka     
MongoDB shell version: 2.4.8
connecting to: akka
> db.stocks.findOne({},{Company: 1, Country: 1, Ticker:1 } )
{
        "_id" : ObjectId("52853800bb1177ca391c17ff"),
        "Ticker" : "A",
        "Country" : "USA",
        "Company" : "Agilent Technologies Inc."
}
>

На данный момент у нас есть наши тестовые данные и мы можем начать создавать наш HTTP-сервер на основе go. Вы можете найти полный код в Gist здесь: https://gist.github.com/josdirksen/071f26a736eca26d7ea4

В следующем разделе мы рассмотрим различные части этой Gist, чтобы объяснить, как настроить HTTP-сервер на основе go.

Основная функция

Когда вы запускаете приложение go, go будет искать основную функцию. Для нашего сервера эта основная функция выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
func main() {
 
    server := http.Server{
        Addr:    ":8000",
        Handler: NewHandler(),
    }
 
    // start listening
    fmt.Println("Started server 2")
    server.ListenAndServe()
 
}

Это настроит сервер для работы на порту 8000, и любой входящий запрос будет обработан экземпляром NewHandler (), который мы создаем в строке 64. Мы запускаем сервер, вызывая функцию server.listenAndServe ().

Теперь давайте посмотрим на наш обработчик, который будет отвечать на запросы.

Структура myHandler

Давайте сначала посмотрим, как выглядит этот обработчик:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Constructor for the server handlers
func NewHandler() *myHandler {
    h := new(myHandler)
    h.defineMappings()
 
    return h
}
 
// Definition of this struct
type myHandler struct {
    // holds the mapping
    mux map[string]func(http.ResponseWriter, *http.Request)
}
 
// functions defined on struct
func (my *myHandler) defineMappings() {
 
    session, err := mgo.Dial("localhost")
    if err != nil {
        panic(err)
    }
 
    // make the mux
    my.mux = make(map[string]func(http.ResponseWriter, *http.Request))
 
    // matching of request path
    my.mux["/hello"] = requestHandler1
    my.mux["/get"] = my.wrap(requestHandler2, session)
}
 
// returns a function so that we can use the normal mux functionality and pass in a shared mongo session
func (my *myHandler) wrap(target func(http.ResponseWriter, *http.Request, *mgo.Session), mongoSession *mgo.Session) func(http.ResponseWriter, *http.Request) {
    return func(resp http.ResponseWriter, req *http.Request) {
        target(resp, req, mongoSession)
    }
}
 
// implements serveHTTP so this struct can act as a http server
func (my *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if h, ok := my.mux[r.URL.String()]; ok {
        // handle paths that are found
        h(w, r)
        return
    } else {
        // handle unhandled paths
        io.WriteString(w, "My server: "+r.URL.String())
    }
}

Давайте разберем это и рассмотрим различные части. Первое, что мы делаем, это определяем конструктор:

1
2
3
4
5
6
func NewHandler() *myHandler {
    h := new(myHandler)
    h.defineMappings()
 
    return h
}

Когда мы вызываем этот конструктор, он создает экземпляр типа myHandler и вызывает функцию defineMappings (). После этого он вернет экземпляр, который мы создали.

Как выглядит тип, который мы создаем:

1
2
3
4
type myHandler struct {
    // holds the mapping
    mux map[string]func(http.ResponseWriter, *http.Request)
}

Как вы можете определить структуру с переменной mux как карту. Эта карта будет содержать наше отображение между путем запроса и функцией, которая может обработать запрос.

В конструкторе мы также вызвали функцию defineMappings. Эта функция, которая определена в нашей структуре myHandler, выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
func (my *myHandler) defineMappings() {
 
    session, err := mgo.Dial("localhost")
    if err != nil {
        panic(err)
    }
 
    // make the mux
    my.mux = make(map[string]func(http.ResponseWriter, *http.Request))
 
    // matching of request path
    my.mux["/hello"] = requestHandler1
    my.mux["/get"] = my.wrap(requestHandler2, session)
}

В этой (плохо названной) функции мы определяем отображение между входящими запросами и конкретной функцией, которая обрабатывает запрос. И в этой функции мы также создаем сеанс для mongoDB, используя функцию mgo.Dial. Как видите, мы определяем requestHandlers двумя разными способами. Обработчик «/ hello» напрямую указывает на функцию, а обработчик пути «/ get» указывает на функцию обтекания, которая также определена в структуре myHandler:

1
2
3
4
5
func (my *myHandler) wrap(target func(http.ResponseWriter, *http.Request, *mgo.Session), mongoSession *mgo.Session) func(http.ResponseWriter, *http.Request) {
    return func(resp http.ResponseWriter, req *http.Request) {
        target(resp, req, mongoSession)
    }
}

Это функция, которая возвращает функцию. Причина, по которой мы это делаем, заключается в том, что мы также хотим передать наш сеанс Монго в обработчик запросов. Поэтому мы создаем пользовательскую функцию-обертку, которая имеет правильную сигнатуру, и просто передаем каждый вызов функции, где мы также предоставляем сеанс Монго. (Обратите внимание, что мы также могли бы изменить реализацию ServeHTTP, которую мы объясним ниже)

Наконец, мы определяем функцию ServeHTTP в нашей структуре. Эта функция вызывается всякий раз, когда мы получаем запрос:

01
02
03
04
05
06
07
08
09
10
func (my *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if h, ok := my.mux[r.URL.String()]; ok {
        // handle paths that are found
        h(w, r)
        return
    } else {
        // handle unhandled paths
        io.WriteString(w, "My server: "+r.URL.String())
    }
}

В этой функции мы проверяем, есть ли совпадение в нашей переменной mux. Если мы это сделаем, мы вызываем настроенную функцию дескриптора. Если нет, мы просто отвечаем простой строкой.

Функции обработки запросов

Давайте начнем с функции handle, которая обрабатывает путь «/ hello»:

1
2
3
func requestHandler1(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello world!")
}

Не может быть проще Мы просто пишем определенную строку как HTTP-ответ. Путь «/ get» более интересен:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func requestHandler2(w http.ResponseWriter, r *http.Request, mongoSession *mgo.Session) {
    c1 := make(chan string)
    c2 := make(chan string)
    c3 := make(chan string)
 
    go query("AAPL", mongoSession, c1)
    go query("GOOG", mongoSession, c2)
    go query("MSFT", mongoSession, c3)
 
    select {
    case data := <-c1:
        io.WriteString(w, data)
    case data := <-c2:
        io.WriteString(w, data)
    case data := <-c3:
        io.WriteString(w, data)
    }
 
}
 
// runs a query against mongodb
func query(ticker string, mongoSession *mgo.Session, c chan string) {
    sessionCopy := mongoSession.Copy()
    defer sessionCopy.Close()
    collection := sessionCopy.DB("akka").C("stocks")
    var result bson.M
    collection.Find(bson.M{"Ticker": ticker}).One(&result)
 
    asString, _ := json.MarshalIndent(result, "", "  ")
 
    amt := time.Duration(rand.Intn(120))
    time.Sleep(time.Millisecond * amt)
    c <- string(asString)
}

Здесь мы используем функциональность канала go для запуска трех запросов одновременно. Мы получаем информацию о тикерах для AAPL, GOOG и MSFT и возвращаем результат по определенному каналу. Когда мы получаем ответ по одному из каналов, мы возвращаем этот ответ. Поэтому каждый раз, когда мы вызываем эту услугу, мы получаем либо результаты AAPL, GOOG или MSFT.

На этом мы завершаем этот первый шаг в go-lang