Using JWT for Authentication in a Golang Application
https://developer.vonage.com/blog/2020/03/13/using-jwt-for-authentication-in-a-golang-application-dr
https://disk.yandex.ru/d/IzZ3Y6P_EAAC6w
https://github.com/victorsteven/jwt-best-practices
Using JWT for Authentication in a Golang Application
https://developer.vonage.com/blog/2020/03/13/using-jwt-for-authentication-in-a-golang-application-dr
https://disk.yandex.ru/d/IzZ3Y6P_EAAC6w
https://github.com/victorsteven/jwt-best-practices
package mux import ( "compress/gzip" "fmt" "log" "mime" "net/http" "path/filepath" "strings" ) func GzipAutoMiddleware(hf http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { accept := r.Header.Get("Accept-Encoding") content := r.Header.Get("Content-Encoding") if strings.Contains(content, "gzip") { reader, e := gzip.NewReader(r.Body) if e != nil { e = fmt.Errorf("reading gzip body failed:%w", e) log.Println(e) http.Error(w, e.Error(), http.StatusBadRequest) return } r.Body = reader } if strings.Contains(accept, "gzip") { ext := filepath.Ext(r.RequestURI) mime := mime.TypeByExtension(ext) if r.RequestURI == "/" || strings.HasPrefix(mime, "text/") || ext == ".json" || ext == ".js" { w.Header().Set("Content-Encoding", "gzip") gw := gzip.NewWriter(w) defer gw.Flush() defer gw.Close() w = &ResponseWriterBuf{ Rw: w, Writer: gw, } } } hf(w, r) } }
Here’s an example of parsing into a struct:
type App struct { Id string `json:"id"` Title string `json:"title"` } data := []byte(` { "id": "k34rAT4", "title": "My Awesome App" } `) var app App err := json.Unmarshal(data, &app)
What you’re left with is app
populated with the parsed JSON that was in data
. You’ll also notice that the go term for parsing json is “Unmarshalling”.
Outputting from a struct works exactly as parsing but in reverse:
data, err := json.Marshal(app)
As with all structs in Go, it’s important to remember that only fields with a capital first letter are visible to external programs like the JSON Marshaller.
You can get rid of the JsonType
as well and just use a slice:
package main
import (
"encoding/json"
"log"
)
func main() {
dataJson := `["1","2","3"]`
var arr []string
_ = json.Unmarshal([]byte(dataJson), &arr)
log.Printf("Unmarshaled: %v", arr)
}
// prints out:
// 2009/11/10 23:00:00 Unmarshaled: [1 2 3]
Code on play: https://play.golang.org/p/GNWlylavam
Пример тестирование API HTTP
package apiserver import ( "bytes" "encoding/json" "fmt" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "rest_api/internal/app/model" "rest_api/internal/app/store/teststore" "testing" ) func TestServer_HandleCreateUser(t *testing.T) { s := newServer(teststore.New()) testCases := []struct { name string payload interface{} expectedCode int }{ { name: "valid", payload: map[string]interface{}{ "name": "Name", "email": "current@mail.com", "password": "123456", }, expectedCode: http.StatusCreated, }, { name: "empty payload", payload: "invalid", expectedCode: http.StatusBadRequest, }, { name: "invalid email", payload: map[string]interface{}{ "name": "Name", "email": "nenormalmail", "password": "12345", }, expectedCode: http.StatusUnprocessableEntity, }, { name: "invalid password", payload: map[string]interface{}{ "name": "Name", "email": "normal@mail.com", "password":"12345", }, expectedCode: http.StatusUnprocessableEntity, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { b := &bytes.Buffer{} json.NewEncoder(b).Encode(tc.payload) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/create", b) s.ServeHTTP(rec, req) assert.Equal(t, tc.expectedCode, rec.Code) }) } } func TestServer_HandleAuthorizeUser(t *testing.T) { s := newServer(teststore.New()) testCases := []struct{ name string payload interface{} expectedCode int }{ { name: "valid", payload: map[string]interface{}{ "email": "test@example.com", "password": "password", }, expectedCode: http.StatusOK, }, { name: "invalid payload", payload: "invalid", expectedCode: http.StatusBadRequest, }, { name: "invalid user", payload: map[string]interface{}{ "email": "test@example.com", "password": "", }, expectedCode: http.StatusForbidden, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { user := map[string]interface{}{ "name": "User", "email": "test@example.com", "password": "password", } resp := struct { Token string `json:"token"` }{} b := &bytes.Buffer{} json.NewEncoder(b).Encode(user) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/create", b) s.ServeHTTP(rec, req) json.NewEncoder(b).Encode(tc.payload) rec = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodPost, "/authorize", b) s.ServeHTTP(rec, req) json.NewDecoder(rec.Body).Decode(resp) recToken := resp.Token assert.Equal(t, tc.expectedCode, rec.Code) assert.NotNil(t, recToken) }) } } func TestServer_HandleCreateArticle(t *testing.T) { s := newServer(teststore.New()) testCases := []struct{ name string payload interface{} expectedCode int }{ { name: "valid", payload: map[string]interface{}{ "article_header": "Test Article", "article_text": "Article test text", "author_id": 1, }, expectedCode: http.StatusCreated, }, { name: "invalid", payload: "invalid", expectedCode: http.StatusBadRequest, }, { name: "invalid article text", payload: map[string]interface{}{ "article_header": "Test Article", "article_text": 7, "author_id": 1, }, expectedCode: http.StatusBadRequest, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { us := map[string]interface{}{ "name": "User", "email": "user@mail.com", "password": "123456", } resp := &struct { User *model.User `json:"user"` Token string `json:"token"` }{} b := &bytes.Buffer{} json.NewEncoder(b).Encode(us) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/create", b) s.ServeHTTP(rec, req) json.NewDecoder(rec.Body).Decode(resp) token := fmt.Sprintf("Baerer %s", resp.Token) json.NewEncoder(b).Encode(tc.payload) req, _ = http.NewRequest(http.MethodPost, "/private/create/article", b) rec = httptest.NewRecorder() req.Header.Add("Authorization", token) s.ServeHTTP(rec, req) assert.Equal(t, tc.expectedCode, rec.Code) }) } } func TestServer_HandleFindArticleByHeading(t *testing.T) { ts := teststore.New() ts.Article().CreateArticle(model.TestArticle(t, 5)) s := newServer(ts) testCases := []struct{ name string payload interface{} expectedCode int }{ { name: "valid", payload: map[string]interface{}{ "article_heading": "TestArticle", }, expectedCode: http.StatusOK, }, { name: "invalid article header", payload: map[string]interface{}{ "article_heading": "Article", }, expectedCode: http.StatusUnprocessableEntity, }, { name: "invalid request", payload: map[string]interface{}{ "article_heading": 1, }, expectedCode: http.StatusBadRequest, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { b := &bytes.Buffer{} json.NewEncoder(b).Encode(tc.payload) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/find/article", b) s.ServeHTTP(rec, req) assert.Equal(t, tc.expectedCode, rec.Code) }) } } func TestServer_HandleChangeArticle(t *testing.T) { ts := teststore.New() article := model.TestArticle(t, 5) ts.Article().CreateArticle(article) s := newServer(ts) testCases := []struct{ name string payload interface{} expectedCode int }{ { name: "valid", payload: map[string]interface{}{ "id" : article.ID, "article_header": "Updated TestArticle", "article_text": "updated article text", }, expectedCode: http.StatusOK, }, { name: "invalid", payload: "invalid", expectedCode: http.StatusBadRequest, }, { name: "invalid id", payload: map[string]interface{}{ "id" : 1, "article_header": "Updated TestArticle", "article_text": "updated article text", }, expectedCode: http.StatusUnprocessableEntity, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { us := map[string]interface{}{ "name": "User", "email": "user@mail.com", "password": "123456", } resp := &struct { User *model.User `json:"user"` Token string `json:"token"` }{} b := &bytes.Buffer{} json.NewEncoder(b).Encode(us) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/create", b) s.ServeHTTP(rec, req) json.NewDecoder(rec.Body).Decode(resp) token := fmt.Sprintf("Baerer %s", resp.Token) json.NewEncoder(b).Encode(tc.payload) req, _ = http.NewRequest(http.MethodPut, "/private/change/article", b) rec = httptest.NewRecorder() req.Header.Add("Authorization", token) s.ServeHTTP(rec, req) assert.Equal(t, tc.expectedCode, rec.Code) }) } } func TestServer_HandleDeleteArticle(t *testing.T) { ts := teststore.New() article := model.TestArticle(t, 5) ts.Article().CreateArticle(article) s := newServer(ts) testCases := []struct{ name string payload interface{} expectedCode int }{ { name: "valid", payload: map[string]interface{}{ "id" : article.ID, }, expectedCode: http.StatusOK, }, { name: "invalid", payload: "invalid", expectedCode: http.StatusBadRequest, }, { name: "invalid id", payload: map[string]interface{}{ "id" : 1, }, expectedCode: http.StatusUnprocessableEntity, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { us := map[string]interface{}{ "name": "User", "email": "user@mail.com", "password": "123456", } resp := &struct { User *model.User `json:"user"` Token string `json:"token"` }{} b := &bytes.Buffer{} json.NewEncoder(b).Encode(us) rec := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/create", b) s.ServeHTTP(rec, req) json.NewDecoder(rec.Body).Decode(resp) token := fmt.Sprintf("Baerer %s", resp.Token) json.NewEncoder(b).Encode(tc.payload) req, _ = http.NewRequest(http.MethodDelete, "/private/delete/article", b) rec = httptest.NewRecorder() req.Header.Add("Authorization", token) s.ServeHTTP(rec, req) assert.Equal(t, tc.expectedCode, rec.Code) }) } }
https://github.com/backspacesh/notebook_api/blob/5bdd97a7b98f85a84d6af4bb51da9d128a520384/internal/app/apiserver/server_internal_test.go
package main import ( "compress/gzip" "io" "log" "net/http" "strings" ) type gzipWriter struct { http.ResponseWriter Writer io.Writer } func (w gzipWriter) Write(b []byte) (int, error) { // w.Writer будет отвечать за gzip-сжатие, поэтому пишем в него return w.Writer.Write(b) } func gzipHandle(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // проверяем, что клиент поддерживает gzip-сжатие if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { // если gzip не поддерживается, передаём управление // дальше без изменений log.Println("no support") next.ServeHTTP(w, r) return } // создаём gzip.Writer поверх текущего w gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed) if err != nil { log.Println("erroe") io.WriteString(w, err.Error()) return } defer gz.Close() w.Header().Set("Content-Encoding", "gzip") // передаём обработчику страницы переменную типа gzipWriter для вывода данных next.ServeHTTP(gzipWriter{ResponseWriter: w, Writer: gz}, r) }) } func main() { mux := http.NewServeMux() mux.HandleFunc("/", defaultHandle()) http.ListenAndServe(":3000", gzipHandle(mux)) } // HandlerSetURL - создаем запись для url func defaultHandle() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // заголов ответа json w.Header().Set("Content-Type", "application/json") // заголов ответа 201 w.WriteHeader(http.StatusCreated) w.Write([]byte("привет")) } }
https://gist.github.com/evgensr/e048f2a85ec77c2effc4d5a3d84be3f4