Как создать веб-приложение с помощью Go, Gin и React

Эта статья изначально была размещена в моем блоге

TL; DR: В этом руководстве я покажу вам, как легко создать веб-приложение с помощью Go и фреймворка Gin и добавить к нему аутентификацию. Посмотрите репозиторий Github, чтобы узнать код, который мы собираемся написать.

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

Особенности джина

Gin - это быстрый, простой, но полнофункциональный и очень эффективный веб-фреймворк для Go. Ознакомьтесь с некоторыми из приведенных ниже функций, которые делают его достойным фреймворком для вашего следующего проекта Golang.

  • Скорость: джин создан для скорости. Платформа предлагает маршрутизацию на основе дерева Radix и небольшой объем памяти. Нет отражения. Прогнозируемая производительность API.
  • Без сбоев: Gin имеет возможность обнаруживать сбои или паники во время выполнения и может восстанавливаться после них. Таким образом, ваше приложение всегда будет доступно.
  • Маршрутизация: Gin предоставляет интерфейс маршрутизации, чтобы вы могли выразить, как должно выглядеть ваше веб-приложение или маршруты API.
  • Проверка JSON: Gin может легко анализировать и проверять запросы JSON, проверяя наличие требуемых значений.
  • Управление ошибками: Gin предоставляет удобный способ собрать все ошибки, возникшие во время HTTP-запроса. В конце концов, промежуточное ПО может записывать их в файл журнала или в базу данных и отправлять их по сети.
  • Встроенный рендеринг: Gin предоставляет простой в использовании API для рендеринга JSON, XML и HTML.

Предпосылки

Чтобы следовать этому руководству, вам потребуется установить Go на вашем компьютере, веб-браузер для просмотра приложения и командную строку для выполнения команд сборки.

Go, или как его обычно называют Golang , - это язык программирования, разработанный Google для создания современного программного обеспечения. Go - это язык, предназначенный для эффективного и быстрого выполнения задач. Ключевые преимущества Go:

  • Строго типизированный и сборщик мусора
  • Молниеносно быстрое время компиляции
  • Встроенный параллелизм
  • Обширная стандартная библиотека

Перейдите в раздел загрузок на веб-сайте Go, чтобы запустить Go на вашем компьютере.

Создание приложения с помощью Gin

Мы создадим простое приложение со списком шуток с помощью Gin . Наше приложение перечислит несколько глупых отцовских шуток. Мы собираемся добавить к нему аутентификацию, чтобы все вошедшие в систему пользователи имели право ставить лайки и просматривать шутки.

Это позволит нам проиллюстрировать, как Gin можно использовать для разработки веб-приложений и / или API.

Мы будем использовать следующие функции, предлагаемые Gin:

  • ПО промежуточного слоя
  • Маршрутизация
  • Группировка маршрутов

На старт, внимание, марш

Мы напишем все наше приложение Go в main.goфайл. Поскольку это небольшое приложение, его будет легко создать с go runпомощью терминала.

Мы создадим новый каталог golang-ginв нашей рабочей области Go, а затем main.goфайл в нем:

$ mkdir -p $GOPATH/src/github.com/user/golang-gin $ cd $GOPATH/src/github.com/user/golang-gin $ touch main.go

Содержание main.goфайла:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Start and run the server router.Run(":3000") }

Нам нужно будет создать еще несколько каталогов для наших статических файлов. В том же каталоге, что и main.goфайл, создадим viewsпапку. В viewsпапке создайте jsпапку и index.htmlфайл в ней.

index.htmlФайл будет очень просто на данный момент:

   Jokeish App   

Welcome to the Jokeish App

Прежде чем мы протестируем то, что у нас есть, давайте установим добавленные зависимости:

$ go get -u github.com/gin-gonic/gin $ go get -u github.com/gin-gonic/contrib/static

Чтобы увидеть, что работает, нам нужно запустить наш сервер, запустив go run main.go.

После запуска приложения перейдите к //localhost:3000в своем браузере. Если все прошло успешно, вы должны увидеть текст заголовка уровня 1 « Добро пожаловать в приложение Jokeish» .

Определение API

Давайте добавим еще немного кода в наш main.goфайл для наших определений API. Мы обновим нашу mainфункцию двумя маршрутами /jokes/и /jokes/like/:jokeIDгруппой маршрутов /api/.

func main() { // ... leave the code above untouched... // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Содержимое main.goфайла должно выглядеть так:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) // Start and run the server router.Run(":3000") } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Давайте снова запустим наше приложение go run main.goи получим доступ к нашим маршрутам. //localhost:3000/api/jokesвернет 200 OKответ заголовка с сообщением jokes handler not implemented yet. Запрос POST для //localhost:3000/api/jokes/like/1возврата 200 OKзаголовка и сообщения Likejoke handler not implemented yet.

Данные анекдотов

Поскольку у нас уже есть набор определений маршрутов, который делает только одно (возвращает ответ JSON), мы немного оживим нашу кодовую базу, добавив к ней еще немного кода.

// ... leave the code above untouched... // Let's create our Jokes struct. This will contain information about a Joke // Joke contains information about a single Joke type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } // We'll create a list of jokes var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } func main() { // ... leave this block untouched... } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { // confirm Joke ID sent is valid // remember to import the `strconv` package if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke, and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes += 1 } } // return a pointer to the updated jokes list c.JSON(http.StatusOK, &jokes) } else { // Joke ID is invalid c.AbortWithStatus(http.StatusNotFound) } } // NB: Replace the JokeHandler and LikeJoke functions in the previous version to the ones above

Когда наш код выглядит хорошо, давайте продолжим и протестируем наш API. Мы можем протестировать с помощью cURLили postman, а затем отправить GETзапрос на //localhost:3000/jokesполучение полного списка шуток и POSTзапрос //localhost:3000/jokes/like/{jokeid}на увеличение количества подобных шуток.

$ curl //localhost:3000/api/jokes $ curl -X POST //localhost:3000/api/jokes/like/4

Создание пользовательского интерфейса (React)

У нас есть API, поэтому давайте создадим интерфейс для представления данных из нашего API. Для этого мы будем использовать React. Мы не будем углубляться в React, так как это выходит за рамки этого руководства. Если вам нужно узнать больше о React, ознакомьтесь с официальным руководством. Вы можете реализовать пользовательский интерфейс с любой удобной для вас интерфейсом.

Настроить

Мы отредактируем index.htmlфайл, чтобы добавить внешние библиотеки, необходимые для запуска React. Затем нам нужно создать app.jsxфайл в views/jsкаталоге, который будет содержать наш код React.

Наш index.htmlфайл должен выглядеть так:

     Jokeish App 

Сборка наших компонентов

В React представления разбиты на компоненты. Нам нужно будет собрать несколько компонентов:

  • Appкомпонент в качестве главного входа , который запускает приложение
  • Homeкомпонент , который столкнется с Некоммерческим зарегистрированными пользователями
  • LoggedInкомпонент с содержанием только видимым пользователям , прошедшим проверку
  • и Jokeкомпонент для отображения списка анекдотов.

Мы запишем все эти компоненты в app.jsxфайл.

Компонент приложения

Этот компонент загружает все наше приложение React. Он решает, какой компонент показывать, в зависимости от того, аутентифицирован ли пользователь. Мы начнем с его базы, а позже обновим ее, добавив больше функций.

class App extends React.Component { render() { if (this.loggedIn) { return (); } else { return (); } } }

Компонент Home

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

class Home extends React.Component { render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ) } }

Компонент LoggedIn

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

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] } } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i){ return (); })} ) } }

Компонент "Шутка"

JokeКомпонент будет содержать информацию о каждом элементе , от шутки отклика , которые будут отображаться.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "" } this.like = this.like.bind(this); } like() { // ... we'll add this block later } render() { return ( #{this.props.joke.id} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Мы написали наши компоненты, поэтому теперь давайте сообщим React, где визуализировать приложение. Мы добавим блок кода ниже в конец нашего app.jsxфайла.

ReactDOM.render(, document.getElementById('app'));

Давайте перезапустим наш сервер Go go run main.goи перейдем к URL-адресу нашего приложения //localhost:3000/. Вы увидите, что Homeкомпонент визуализируется.

Защита нашего приложения для шуток с помощью Auth0

Auth0 выдает веб-токены JSON при каждом входе в систему для ваших пользователей. Это означает, что у вас может быть надежная инфраструктура идентификации, включая единый вход, управление пользователями, поддержку поставщиков социальной идентификации (Facebook, Github, Twitter и т. Д.), Поставщиков корпоративной идентификации (Active Directory, LDAP, SAML и т. Д.) и ваша собственная база данных пользователей с помощью всего нескольких строк кода.

Мы можем легко настроить аутентификацию в нашем приложении GIN с помощью Auth0. Вам понадобится учетная запись, чтобы следовать этой части. Если у вас еще нет учетной записи Auth0, зарегистрируйтесь сейчас.

Отказ от ответственности: это не спонсируемый контент.

Создание клиента API

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

Чтобы создать новый API, перейдите в раздел API на панели инструментов и нажмите кнопку « Создать API» .

Выберите имя API и идентификатор . Идентификатор будет аудиторией для промежуточного программного обеспечения. Подписание Алгоритм должен быть RS256 .

Чтобы создать нового клиента, перейдите в раздел клиентов на панели управления и нажмите кнопку « Создать клиента» . Выберите тип Regular Web Applications.

После создания клиента обратите внимание на client_idи client_secret, поскольку они нам понадобятся позже.

Мы должны добавить учетные данные, необходимые для нашего API, в переменную среды. В корневом каталоге создайте новый файл .envи добавьте к нему следующее с деталями из панели инструментов Auth0:

export export export AUTH0_DOMAIN="yourdomain.auth0.com" export

Защита наших конечных точек API

В настоящее время наш API открыт для всего мира. Нам необходимо защитить наши конечные точки, чтобы только авторизованные пользователи могли получить к ним доступ.

Мы собираемся использовать промежуточное ПО JWT для проверки действительного веб-токена JSON для каждого запроса, попадающего в наши конечные точки.

Создадим наше промежуточное ПО:

// ... var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) // register our actual jwtMiddleware jwtMiddleWare = jwtMiddleware // ... the rest of the code below this function doesn't change yet } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } }

В приведенном выше коде у нас есть новая jwtMiddleWareпеременная, которая инициализируется в mainфункции. Он используется в authMiddlewareсредней функции.

Если вы заметили, мы извлекаем наши учетные данные на стороне сервера из переменной среды (один из принципов 12-факторного приложения ). Наше промежуточное программное обеспечение проверяет и получает токен из запроса и вызывает jwtMiddleWare.CheckJWTметод для проверки отправленного токена.

Также напишем функцию для возврата веб-ключей JSON:

// ... the code above is untouched... // Jwks stores a slice of JSON Web Keys type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } func main() { // ... the code in this method is untouched... } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key.") } return cert, nil }

Использование промежуточного программного обеспечения JWT

Использовать промежуточное ПО очень просто. Мы просто передаем его как параметр в определение нашего маршрута.

... api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) ...

Наш main.goфайл должен выглядеть так:

package main import ( "encoding/json" "errors" "fmt" "log" "net/http" "os" "strconv" jwtmiddleware "github.com/auth0/go-jwt-middleware" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) type Response struct { Message string `json:"message"` } type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } /** we'll create a list of jokes */ var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) jwtMiddleWare = jwtMiddleware // Set the router as the default one shipped with Gin router := gin.Default() // Serve the frontend router.Use(static.Serve("/", static.LocalFile("./views", true))) api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) } // Start the app router.Run(":3000") } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key") } return cert, nil } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } } // JokeHandler returns a list of jokes available (in memory) func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } func LikeJoke(c *gin.Context) { // Check joke ID is valid if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes = jokes[i].Likes + 1 } } c.JSON(http.StatusOK, &jokes) } else { // the jokes ID is invalid c.AbortWithStatus(http.StatusNotFound) } }

Установим jwtmiddlewareбиблиотеки:

$ go get -u github.com/auth0/go-jwt-middleware $ go get -u github.com/dgrijalva/jwt-go

Давайте создадим наш файл среды и перезапустим наш сервер приложений:

$ source .env $ go run main.go

Теперь, если мы попытаемся получить доступ к любой из конечных точек, мы столкнемся с 401 Unauthorizedошибкой. Это потому, что нам нужно отправить токен с запросом.

Войти с Auth0 и React

Давайте внедрим систему входа в систему, чтобы пользователи могли входить в систему или создавать учетные записи и получать доступ к нашим шуткам. Мы добавим в наш app.jsxфайл следующие учетные данные Auth0:

  • AUTH0_CLIENT_ID
  • AUTH0_DOMAIN
  • AUTH0_CALLBACK_URL - URL-адрес вашего приложения
  • AUTH0_API_AUDIENCE
Вы можете найти AUTH0_CLIENT_ID, AUTH0_DOMAINи AUTH0_API_AUDIENCEданные из приборной панели управления Auth0.

Нам нужно установить, на callbackчто Auth0 перенаправляет. Перейдите в раздел «Клиенты» на панели управления. В настройках зададим обратный вызов //localhost:3000:

Имея учетные данные, давайте обновим наши компоненты React.

Компонент приложения

const AUTH0_CLIENT_ID = "aIAOt9fkMZKrNsSsFqbKj5KTI0ObTDPP"; const AUTH0_DOMAIN = "hakaselabs.auth0.com"; const AUTH0_CALLBACK_URL = location.href; const AUTH0_API_AUDIENCE = "golang-gin"; class App extends React.Component { parseHash() { this.auth0 = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID }); this.auth0.parseHash(window.location.hash, (err, authResult) => { if (err) { return console.log(err); } if ( authResult !== null && authResult.accessToken !== null && authResult.idToken !== null ) { localStorage.setItem("access_token", authResult.accessToken); localStorage.setItem("id_token", authResult.idToken); localStorage.setItem( "profile", JSON.stringify(authResult.idTokenPayload) ); window.location = window.location.href.substr( 0, window.location.href.indexOf("#") ); } }); } setup() { $.ajaxSetup({ beforeSend: (r) => { if (localStorage.getItem("access_token")) { r.setRequestHeader( "Authorization", "Bearer " + localStorage.getItem("access_token") ); } } }); } setState() { let idToken = localStorage.getItem("id_token"); if (idToken) { this.loggedIn = true; } else { this.loggedIn = false; } } componentWillMount() { this.setup(); this.parseHash(); this.setState(); } render() { if (this.loggedIn) { return ; } return ; } }

Мы обновили компонент App с тремя компонентами метод ( setup, parseHashи setState), и методом жизненного цикла componentWillMount. parseHashМетод инициализирует auth0webAuthклиент и анализирует хэш более читаемый формат, сохраняя их в localSt. Чтобы отобразить экран блокировки, захватите и сохраните токен пользователя и добавьте правильный заголовок авторизации к любым запросам к нашему API.

Домашний компонент

Наш компонент Home будет обновлен. Мы добавим функциональность для authenticateметода, который будет запускать отображение размещенного экрана блокировки и позволять нашим пользователям входить в систему или регистрироваться.

class Home extends React.Component { constructor(props) { super(props); this.authenticate = this.authenticate.bind(this); } authenticate() { this.WebAuth = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID, scope: "openid profile", audience: AUTH0_API_AUDIENCE, responseType: "token id_token", redirectUri: AUTH0_CALLBACK_URL }); this.WebAuth.authorize(); } render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ); } }

Компонент LoggedIn

Мы обновим LoggedInкомпонент, чтобы он общался с нашим API и снимал все шутки. Он будет проходить каждую шутку , как propк Jokeкомпоненту, который делает панель начальной загрузки. Напишем те:

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] }; this.serverRequest = this.serverRequest.bind(this); this.logout = this.logout.bind(this); } logout() { localStorage.removeItem("id_token"); localStorage.removeItem("access_token"); localStorage.removeItem("profile"); location.reload(); } serverRequest() { $.get("//localhost:3000/api/jokes", res => { this.setState({ jokes: res }); }); } componentDidMount() { this.serverRequest(); } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i) { return ; })} ); } }

Шутка составляющая

Мы также обновим Jokeкомпонент, чтобы отформатировать каждый элемент Joke, переданный ему из Parent compoent ( LoggedIn). Мы также добавим likeметод, который будет увеличивать количество подобных шуток.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "", jokes: [] }; this.like = this.like.bind(this); this.serverRequest = this.serverRequest.bind(this); } like() { let joke = this.props.joke; this.serverRequest(joke); } serverRequest(joke) { $.post( "//localhost:3000/api/jokes/like/" + joke.id, { like: 1 }, res => { console.log("res... ", res); this.setState({ liked: "Liked!", jokes: res }); this.props.jokes = res; } ); } render() { return ( #{this.props.joke.id}{" "} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Собираем все вместе

Завершив UI и API, мы можем протестировать наше приложение. Мы начнем с загрузки нашего сервера source .env && go run main.go, а затем перейдем к нему //localhost:3000из любого браузера. Вы должны увидеть Homeкомпонент с кнопкой входа. При нажатии на кнопку входа выполняется перенаправление на размещенную страницу блокировки (создание учетной записи или входа в систему) для продолжения использования приложения.

Дом:

Размещенный экран блокировки Auth0:

Просмотр приложения LoggedIn:

Вывод

Поздравляю! Вы узнали, как создать приложение и API с помощью Go и фреймворка Gin.

Я пропустил что-то важное? Сообщите мне об этом в комментариях.

Вы можете поздороваться со мной в Twitter @codehakase