go-restful — это фреймворк веб-сервисов для языка программирования Go. Это упрощает создание RESTful API с использованием простого выразительного синтаксиса.

Вот некоторые из ключевых особенностей go-restful:

  • Он предоставляет декларативный способ определения маршрутов для вашего API, включая поддержку параметров пути и параметров строки запроса.
  • Он включает встроенную поддержку кодирования и декодирования тела запроса и ответа в различных форматах, включая JSON и XML.
  • Он включает поддержку промежуточного программного обеспечения для таких задач, как аутентификация, ограничение скорости и ведение журнала.
  • Он включает встроенную поддержку WebSocket, что позволяет легко создавать интерактивные API в режиме реального времени.

В целом, go-restful — это мощный инструмент для создания RESTful API в Go, который широко используется в сообществе Go.

Прежде чем перейти к примеру, нам нужно установить базу данных с именем SQLite3 для нашего REST API с помощью go-restful. Шаги установки следующие:

В Ubuntu выполните эту команду:

apt-get install sqlite3 libsqlite3-dev

В OS X вы можете использовать команду brew для установки SQLite3:

 brew install sqlite3

Теперь установите пакет go-restful с помощью следующей команды get:

go get github.com/emicklei/go-restful

Пакет sqlite3 в Go представляет собой оболочку библиотеки SQLite, написанной на C. Пакет go-sqlite3 включает оболочку Cgo, которая позволяет коду Go вызывать библиотеку SQLite.

Чтобы собрать пакет go-sqlite3, компилятор C (например, GCC) необходим для компиляции кода C в оболочке. Полученные объектные файлы затем связываются с окончательным бинарным файлом Go.

Если вы используете готовую версию пакета go-sqlite3, вам не нужно устанавливать GCC на свой компьютер. Однако, если вы собираете пакет из исходного кода, вам потребуется установить GCC (или другой совместимый компилятор C).

Давайте создадим простой ping-сервер, который возвращает время сервера клиенту, используя go-restful:

package main

import (
     "fmt"
     "time"
     "net/http"
     "github.com/emicklei/go-restful"
     "io"
 )

func main() {
     webservice := new(restful.WebService)
     webservice.Route(webservice.GET("/ping").To(pingTime))
     restful.Add(webservice)
     http.ListenAndServe(":8000", nil)
}

func pingTime(req *restful.Request, resp *restful.Response) {
     io.WriteString(resp, fmt.Sprintf("%s", time.Now()))
}

Выход:

Nithin@NITHIN-KUMAR MINGW64 ~/go/src
$ curl https://localhost:8000/ping
2023-01-06 17:25:31.2270481 +0530 IST m=+19.603182401

Разбивка кода:

Функция main создает новый экземпляр restful.WebService и добавляет к нему маршрут с помощью метода Route. Этот маршрут представляет собой запрос GET к пути '/ping', который будет выполнять функцию pingTime при ее вызове.

Метод restful.Add регистрирует веб-службу со значением по умолчанию restful.Container, а http.ListenAndServe запускает HTTP-сервер на указанном порту.

Функция pingTime — это обработчик маршрута, который записывает текущее время в ответ с помощью функции io.WriteString.

Операции CRUD и основы SQLite3

Чтобы использовать SQLite3 в Go, вам потребуется импортировать пакет «github.com/mattn/go-sqlite3».

go get github.com/mattn/go-sqlite3

Пакет «github.com/mattn/go-sqlite3» представляет собой оболочку пакета «database/sql», который является стандартным интерфейсом базы данных в Go.

Одним из преимуществ использования этой библиотеки является то, что она позволяет использовать тот же интерфейс «database/sql» для взаимодействия с базой данных SQLite, что и с другими системами баз данных. , такие как MySQL или PostgreSQL.

Это означает, что вы можете использовать те же функции и концепции для работы с SQLite, что и с другими базами данных, без необходимости изучения нового набора функций или API-интерфейсов, специфичных для SQLite.

import "database/sql"

Теперь мы можем создать драйвер базы данных, а затем выполнять на нем команды SQL, используя метод под названием Query:

package main

import (
 "database/sql"
 "fmt"
 "log"
 _ "github.com/mattn/go-sqlite3"
)

func main() {
 // Open a connection to the database
     db, err := sql.Open("sqlite3", "./test.db")
     if err != nil {
        log.Fatal(err)
     }
       defer db.Close()
    
     // Create a table
     _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)`)
     if err != nil {
        log.Fatal(err)
     }
    
     // Insert a row
     _, err = db.Exec(`INSERT INTO users (name) VALUES ("Alice")`)
     if err != nil {
        log.Fatal(err)
     }
    
     // Read a row
     var name string
     err = db.QueryRow(`SELECT name FROM users WHERE id = 1`).Scan(&name)
     if err != nil {
        log.Fatal(err)
     }
     fmt.Println(name) // Output: Alice
    
     // Update a row
     _, err = db.Exec(`UPDATE users SET name = "Bob" WHERE id = 1`)
     if err != nil {
        log.Fatal(err)
     }
    
     // Read the updated row
     err = db.QueryRow(`SELECT name FROM users WHERE id = 1`).Scan(&name)
     if err != nil {
        log.Fatal(err)
     }
     fmt.Println(name) // Output: Bob
    
     // Delete a row
     _, err = db.Exec(`DELETE FROM users WHERE id = 1`)
     if err != nil {
        log.Fatal(err)
     }
    
     // Read the deleted row (should return an error)
     err = db.QueryRow(`SELECT name FROM users WHERE id = 1`).Scan(&name)
     if err == sql.ErrNoRows {
        fmt.Println("Expected error: no rows were returned")
     } else if err != nil {
        log.Fatal(err)
     }
}

Выход:

Nithin@NITHIN-KUMAR MINGW64 ~/go/src/github.com/nithin/go-restfull_framework
$ go run sqlite3CRUD.go 
Alice
Bob
Expected error: no rows were returned

Создание простого API фильмов с помощью go-restful

Дорожная карта выглядит следующим образом:

  1. Разработайте документ REST API.
  2. Создание моделей для базы данных.
  3. Реализуйте логику API.

Проектная спецификация

Перед созданием любого API важно иметь четкое представление о его спецификациях. Это часто можно зафиксировать в форме проектного документа, в котором описываются различные конечные точки, полезные нагрузки запросов и ответов и другие детали API.

Мы собираемся создать макет проекта для этого API. Макет проекта будет выглядеть следующим образом:

/src/github.com/nithin/

|-- dbutils
|   |__ init-tables.go
|   |__ models.go
|
|-- movieAPI
|   |__main.go
|
|___movie.db

movieAPI — исходный код нашего проекта, а dbutils — наш собственный пакет для обработки служебных функций инициализации базы данных. Начнем с файла dbutils/models.go.

package dbutils

const movie = `
   CREATE TABLE IF NOT EXISTS movie(
   ID INTEGER PRIMARY KEY AUTOINCREMENT,
   MOVIE_NAME VARCHAR(64) NULL,
   DESCIRPTION VARCHAR(128) NULL,
   SHOWTIME TIME NULL
 )
`

Теперь давайте добавим код для инициализации базы данных (создания таблиц) в файл init-tables.go:

package dbutils

import (
     "database/sql"
     "log"
)

func Initialize(dbDriver *sql.DB) {

     statement, driverError := dbDriver.Prepare(movie)

     if driverError != nil {
        log.Println(driverError)
     }

     // Create movie table
     _, statementError := statement.Exec()
     if statementError != nil {
      log.Println("Table already exists!")
     }
     statement, _ = dbDriver.Prepare(ticket)
     statement.Exec()
     log.Println("All tables created/initialized successfully!")

}

Теперь наш первоначальный пакет готов. Вы можете создать объектный код для этого пакета, используя следующую команду:

go build github.com/nithin/dbutils

давайте напишем простую основную программу, которая импортирует функцию Initialize из пакета dbutils. Назовем файл main.go:

package main

import (
     "database/sql"
     "log"
     _ "github.com/mattn/go-sqlite3"
     "github.com/nithin/dbutils"
)
func main() {

     // Connect to Database
     db, err := sql.Open("sqlite3", "./movieapi.db")

     if err != nil {
       log.Println("Driver creation failed!")
     }

     // Create tables
     dbutils.Initialize(db)

}

И запустите программу из каталога movieAPI с помощью следующей команды:

Вывод, который вы видите, должен быть примерно таким:

мы добавили код для создания драйвера базы данных и передали задачу создания таблицы в функцию Initialize из пакета dbutils.

мы можем сделать это прямо в нашей основной программе, но лучше разбить логику на несколько пакетов и компонентов. Теперь мы расширим этот простой макет, чтобы создать API с помощью пакета go-restful. .

нам нужно несколько структур для обработки данных, входящих и исходящих из базы данных. Для всех моделей должны быть держатели данных.

var DB *sql.DB

type Movie struct {
   ID int
   Title string
   Descirption string
   Showtime time.Time
}

Глобальная переменная DB используется для хранения драйвера базы данных. Упомянутые структуры предназначены для отражения структуры соответствующих таблиц в базе данных SQL. Тип структуры time.Time в Go способен хранить поле TIME из базы данных.

Следующий код создает контейнер для API с помощью go-restful и регистрирует в нем веб-службы. Функция для этого называется Register:

func (m *Movie) Register(container *restful.Container) {
     wsContainer := new(restful.WebService)
     wsContainer.
            Path("/v1/movies").
            Consumes(restful.MIME_JSON).
            Produces(restful.MIME_JSON)
    
     wsContainer.Route(wsContainer.GET("").To(m.getMovieList))
     wsContainer.Route(wsContainer.GET("/{movie-id}").To(m.getMovie))
     wsContainer.Route(wsContainer.POST("").To(m.createMovie))
     wsContainer.Route(wsContainer.DELETE("/{movie-id}").To(m.removeMovie))
     container.Add(wsContainer)
}

В go-restful веб-сервисы основаны на ресурсах, а структура Movie имеет метод с именем Register, который принимает restful.Container в качестве аргумента. Этот метод создает новый restful.WebService с именем wsContainer для обработки ресурса Movie и добавляет к нему маршруты метода HTTP.

Маршруты указывают конечную точку URL-адреса и любые параметры пути или запроса и связаны с обработчиками функций, такими как getMovie, createMovie и removeMovie для методов GET, POST и DELETE соответственно.

Path("/v1/movies").
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON)

Эти 3 утверждения говорят о том, что API будет принимать и возвращать данные только JSONType в запросе. Для всех остальных типов он автоматически возвращает ошибку 415 — Media Not Supported.

Теперь давайте определим обработчики функций:

func(t Movie) createMovie(request * restful.Request, response * restful.Response) {

    log.Println(request.Request.Body)
    decoder: = json.NewDecoder(request.Request.Body)
    var b Movie
    _ = decoder.Decode( & b)
        //log.Println(b.Moviename, b.Show)
    var existingMovie Movie
    err: = DB.QueryRow("SELECT * FROM movie WHERE MOVIE_NAME=?", b.Moviename).Scan( & existingMovie.ID, & existingMovie.Moviename, & existingMovie.Descirption, & existingMovie.Show)

    if err == nil {
        // If no error is returned, that means a movie with the same name already exists
        response.AddHeader("Content-Type", "text/plain")
        response.WriteErrorString(http.StatusConflict, "A movie with the same name already exists")

    } else if err == sql.ErrNoRows {

        statement, _: = DB.Prepare("insert into movie (MOVIE_NAME,DESCIRPTION,SHOW) values (?,?,?)")
        result, err: = statement.Exec(b.Moviename, b.Descirption, b.Show)
        if err == nil {
            newID, _: = result.LastInsertId()
            b.ID = int(newID)
            response.WriteHeaderAndEntity(http.StatusCreated, b)
        } else {
            response.AddHeader("Content-Type", "text/plain")
            response.WriteErrorString(http.StatusInternalServerError, err.Error())
        }
    }

}

Это функция Go для обработки запроса RESTful на создание нового фильма. Сначала он считывает тело запроса, которое должно быть в формате JSON, и декодирует его в экземпляр структуры Movie. Затем он проверяет, существует ли фильм с таким же именем, запрашивая таблицу «фильм» в базе данных.

Если фильм с таким именем уже существует, он записывает в ответ сообщение об ошибке, указывающее, что произошел конфликт. Если фильм с таким именем не найден, он подготавливает оператор SQL для вставки нового фильма в таблицу «movie», а затем выполняет оператор.

Если вставка прошла успешно, он записывает созданный фильм, включая новый идентификатор, присвоенный базой данных, в ответ с кодом состояния 201 (Создано). В случае ошибки он записывает в ответ сообщение об ошибке, указывающее на внутреннюю ошибку сервера.

Выход:

Используйте команду Curl для POST данных

 curl -X POST  https://localhost:8000/v1/movies  
-H 'cache-control: no-cache'  -H 'content-type: application/json' 
-d '{"Moviename": "Fight Club", "DESCIRPTION":"Triller","SHOW": "Morning"}'

Теперь давайте напишем функцию, которая проверяет наличие определенного фильма.

func (m Movie) getMovie(request *restful.Request, response *restful.Response) {

   id := request.PathParameter("movie-id")
   err := DB.QueryRow("select ID , MOVIE_NAME , DESCIRPTION ,SHOW FROM movie where id=?", id).Scan(&m.ID, &m.Moviename, &m.Descirption, &m.Show)
  
   if err != nil {
      log.Println(err)
      response.AddHeader("Content-Type", "text/plain")
      response.WriteErrorString(http.StatusNotFound, "Movie could not be found.")
   } else {
      response.WriteEntity(m)
   }
}

Эта функция представляет собой функцию-обработчик HTTP, которая обрабатывает запрос GET к определенной конечной точке "/{movie-id}", извлекая идентификатор фильма из параметра пути запроса и проверяя идентификатор фильма, если он присутствует в БД.

если запрос выполнен успешно, он записывает структуру обратно клиенту в качестве ответа с помощью функции response.WriteEntity(m).

ВЫХОД:

Используйте Curl, чтобы ПОЛУЧИТЬ данные

$ curl https://localhost:8000/v1/movies/4

обработчик функции для удаления фильма из базы данных.

func (m Movie) removeMovie(request *restful.Request, response *restful.Response) {
   id := request.PathParameter("movie-id")
   statement, _ := DB.Prepare("delete from movie where id=?")
   _, err := statement.Exec(id)

   if err == nil {
      response.WriteHeader(http.StatusOK)
   } else {
      response.AddHeader("Content-Type", "text/plain")
      response.WriteErrorString(http.StatusInternalServerError,
      err.Error())
   }
}

Это функция Go для обработки запроса RESTful на удаление фильма по идентификатору. Сначала он извлекает идентификатор фильма из параметра пути запроса. Затем он подготавливает оператор SQL для удаления фильма из таблицы «movie» с соответствующим идентификатором, а затем выполняет оператор.

ВЫХОД:

curl -X DELETE https://localhost:8000/v1/movies/4

функция для получения всего списка фильмов в базе данных.

func (m Movie) getMovieList(request *restful.Request, response *restful.Response) {
     rows, err := DB.Query("SELECT ID, MOVIE_NAME, DESCIRPTION, SHOW FROM movie")
     if err != nil {
          log.Println(err)
          response.AddHeader("Content-Type", "text/plain")
          response.WriteErrorString(http.StatusInternalServerError, "Error retrieving movies.")
          return
     }
           defer rows.Close()
    
     var movies []Movie

     for rows.Next() {

      var movie Movie
      err := rows.Scan(&movie.ID, &movie.Moviename, &movie.Descirption, &movie.Show)

      if err != nil {
       log.Println(err)
       response.AddHeader("Content-Type", "text/plain")
       response.WriteErrorString(http.StatusInternalServerError, "Error retrieving movies.")
       return
      }
      movies = append(movies, movie)
     }
     response.WriteEntity(movies)
}

Функция извлекает строки из базы данных, просматривает строки и присваивает значения соответствующим полям структуры типа «Фильм».

Затем структуры добавляются к фрагменту структур, называемому «фильмы». Функция записывает в ответ список фильмов и отправляет его обратно клиенту.

Если при извлечении или сканировании строк возникают какие-либо ошибки, к ответу добавляется сообщение об ошибке, которое отправляется обратно клиенту.

В заключение, go-restful — это мощный и эффективный инструмент для создания RESTful API в Go. Его легкий и низкоуровневый дизайн позволяет легко преобразовывать ресурсы в потребляемые конечные точки, избегая при этом ненужных накладных расходов или раздувания.

Ориентация библиотеки на простоту и производительность делает ее отличным выбором для разработки высокопроизводительных масштабируемых API. Независимо от того, являетесь ли вы опытным разработчиком Go или только начинаете знакомиться с языком, go-restful определенно стоит проверить.