go luhn

проверка Луна

 



код луна

https://go.dev/play/p/S_nrTTrk0Wq

package main

import (
	"fmt"

	"github.com/theplant/luhn"
)

func main() {
	fmt.Println(
		luhn.Valid(6174570601580),
		luhn.Valid(422466257468622),
		luhn.Valid(2352238521358),
		luhn.Valid(7162683784855),
	)
}

Golang mux gzip

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)
	}
}

Go and JSON

Parsing into a Struct

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”.

Rendering from a Struct

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

Golang тестирование http

Пример тестирование 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

gzip golang

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

HTTP-REST-API

Написал http-rest-api на go

https://github.com/evgensr/http-rest-api

Отработка

  • работа с флагами
  • работа с конфигом toml
  • работа с базой данных postgres
  • тестирование
  • логирование с разными уровнями
  • миграция
  • реализация store в памяти
  • сессии через cookie
  • middleware которы добавляет к каждому запросу uuid и логирует его с выводом времени исполнения
  • middleware которы реализует приватные методы api
  • CORS
  • реализация собственных ошибок