Skip to content

Commit 64932fd

Browse files
authored
Merge pull request #178 from SevereCloud/dev-v2.13.0
v2.13.0
2 parents 5ec50a1 + 15a6084 commit 64932fd

35 files changed

+1261
-45
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ Version API 5.131.
1717

1818
- [API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api)
1919
- 500+ methods
20-
- Ability to change the request handler
2120
- Ability to modify HTTP client
2221
- Request Limiter
22+
- Support [zstd](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api#VK.EnableZstd)
23+
and [MessagePack](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api#VK.EnableMessagePack)
2324
- Token pool
2425
- [OAuth](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/api/oauth)
2526
- [Callback API](https://pkg.go.dev/github.com/SevereCloud/vksdk/v2/callback)

api/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,54 @@ if errors.As(err, &e) {
8080

8181
Для Execute существует отдельная ошибка `ExecuteErrors`
8282

83+
### Поддержка MessagePack и zstd
84+
85+
> Результат перехода с gzip (JSON) на zstd (msgpack):
86+
>
87+
> - в 7 раз быстрее сжатие (–1 мкс);
88+
> - на 10% меньше размер данных (8 Кбайт вместо 9 Кбайт);
89+
> - продуктовый эффект не статзначимый :(
90+
>
91+
> [Как мы отказались от JPEG, JSON, TCP и ускорили ВКонтакте в два раза](https://habr.com/ru/company/vk/blog/594633/)
92+
93+
VK API способно возвращать ответ в виде [MessagePack](https://msgpack.org/).
94+
Это эффективный формат двоичной сериализации, похожий на JSON, только быстрее
95+
и меньше по размеру.
96+
97+
ВНИМАНИЕ, C MessagePack НЕКОТОРЫЕ МЕТОДЫ МОГУТ ВОЗВРАЩАТЬ
98+
СЛОМАННУЮ КОДИРОВКУ.
99+
100+
Для сжатия, вместо классического gzip, можно использовать
101+
[zstd](https://github.com/facebook/zstd). Сейчас vksdk поддерживает zstd без
102+
словаря. Если кто знает как получать словарь,
103+
[отпишитесь сюда](https://github.com/SevereCloud/vksdk/issues/180).
104+
105+
```go
106+
vk := api.NewVK(os.Getenv("USER_TOKEN"))
107+
108+
method := "store.getStickersKeywords"
109+
params := api.Params{
110+
"aliases": true,
111+
"all_products": true,
112+
"need_stickers": true,
113+
}
114+
115+
r, err := vk.Request(method, params) // Content-Length: 44758
116+
if err != nil {
117+
log.Fatal(err)
118+
}
119+
log.Println("json:", len(r)) // json: 814231
120+
121+
vk.EnableMessagePack() // Включаем поддержку MessagePack
122+
vk.EnableZstd() // Включаем поддержку zstd
123+
124+
r, err = vk.Request(method, params) // Content-Length: 35755
125+
if err != nil {
126+
log.Fatal(err)
127+
}
128+
log.Println("msgpack:", len(r)) // msgpack: 650775
129+
```
130+
83131
### Запрос любого метода
84132

85133
Пример запроса [users.get](https://vk.com/dev/users.get)

api/ads.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package api // import "github.com/SevereCloud/vksdk/v2/api"
22

33
import (
4+
"bytes"
45
"encoding/json"
56

67
"github.com/SevereCloud/vksdk/v2/object"
8+
"github.com/vmihailenco/msgpack/v5"
79
)
810

911
// AdsAddOfficeUsersItem struct.
@@ -21,6 +23,23 @@ func (r *AdsAddOfficeUsersItem) UnmarshalJSON(data []byte) (err error) {
2123
return
2224
}
2325

26+
// DecodeMsgpack func.
27+
func (r *AdsAddOfficeUsersItem) DecodeMsgpack(dec *msgpack.Decoder) error {
28+
data, err := dec.DecodeRaw()
29+
if err != nil {
30+
return err
31+
}
32+
33+
if msgpack.Unmarshal(data, &r.OK) != nil {
34+
d := msgpack.NewDecoder(bytes.NewReader(data))
35+
d.SetCustomStructTag("json")
36+
37+
return d.Decode(&r.Error)
38+
}
39+
40+
return nil
41+
}
42+
2443
// AdsAddOfficeUsersResponse struct.
2544
type AdsAddOfficeUsersResponse []AdsAddOfficeUsersItem
2645

api/ads_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55

66
"github.com/SevereCloud/vksdk/v2/api"
77
"github.com/stretchr/testify/assert"
8+
"github.com/vmihailenco/msgpack/v5"
9+
"github.com/vmihailenco/msgpack/v5/msgpcode"
810
)
911

1012
func TestAdsResponse_UnmarshalJSON(t *testing.T) {
@@ -32,6 +34,46 @@ func TestAdsResponse_UnmarshalJSON(t *testing.T) {
3234
)
3335
}
3436

37+
func TestAdsResponse_DecodeMsgpack(t *testing.T) {
38+
t.Parallel()
39+
40+
f := func(data []byte, expected api.AdsAddOfficeUsersItem, wantErr string) {
41+
var r api.AdsAddOfficeUsersItem
42+
43+
err := msgpack.Unmarshal(data, &r)
44+
if err != nil || wantErr != "" {
45+
assert.EqualError(t, err, wantErr)
46+
}
47+
48+
assert.Equal(t, expected, r)
49+
}
50+
51+
f([]byte{msgpcode.False}, api.AdsAddOfficeUsersItem{OK: false}, "")
52+
f([]byte{msgpcode.True}, api.AdsAddOfficeUsersItem{OK: true}, "")
53+
f(
54+
[]byte{
55+
0x82, 0xAA, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x63, 0x6F, 0x64,
56+
0x65, 0x64, 0xAA, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x64, 0x65,
57+
0x73, 0x63, 0xD9, 0x48, 0x4F, 0x6E, 0x65, 0x20, 0x6F, 0x66, 0x20,
58+
0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74,
59+
0x65, 0x72, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69,
60+
0x65, 0x64, 0x20, 0x77, 0x61, 0x73, 0x20, 0x6D, 0x69, 0x73, 0x73,
61+
0x69, 0x6E, 0x67, 0x20, 0x6F, 0x72, 0x20, 0x69, 0x6E, 0x76, 0x61,
62+
0x6C, 0x69, 0x64, 0x3A, 0x20, 0x64, 0x61, 0x74, 0x61, 0x5B, 0x31,
63+
0x5D, 0x5B, 0x75, 0x73, 0x65, 0x72, 0x5F, 0x69, 0x64, 0x5D,
64+
},
65+
api.AdsAddOfficeUsersItem{
66+
OK: false,
67+
Error: api.AdsError{
68+
Code: 100,
69+
Desc: "One of the parameters specified was missing or invalid: data[1][user_id]",
70+
},
71+
},
72+
"",
73+
)
74+
f(nil, api.AdsAddOfficeUsersItem{}, "EOF")
75+
}
76+
3577
func TestVK_AdsGetAccounts(t *testing.T) {
3678
t.Parallel()
3779

api/api.go

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ package api // import "github.com/SevereCloud/vksdk/v2/api"
77

88
import (
99
"bytes"
10+
"compress/gzip"
1011
"context"
1112
"encoding/json"
1213
"fmt"
14+
"io"
1315
"mime"
1416
"net/http"
1517
"net/url"
@@ -21,6 +23,8 @@ import (
2123
"github.com/SevereCloud/vksdk/v2"
2224
"github.com/SevereCloud/vksdk/v2/internal"
2325
"github.com/SevereCloud/vksdk/v2/object"
26+
"github.com/klauspost/compress/zstd"
27+
"github.com/vmihailenco/msgpack/v5"
2428
)
2529

2630
// Api constants.
@@ -91,16 +95,19 @@ type VK struct {
9195
UserAgent string
9296
Handler func(method string, params ...Params) (Response, error)
9397

98+
msgpack bool
99+
zstd bool
100+
94101
mux sync.Mutex
95102
lastTime time.Time
96103
rps int
97104
}
98105

99106
// Response struct.
100107
type Response struct {
101-
Response json.RawMessage `json:"response"`
102-
Error Error `json:"error"`
103-
ExecuteErrors ExecuteErrors `json:"execute_errors"`
108+
Response object.RawMessage `json:"response"`
109+
Error Error `json:"error"`
110+
ExecuteErrors ExecuteErrors `json:"execute_errors"`
104111
}
105112

106113
// NewVK returns a new VK.
@@ -243,24 +250,52 @@ func (vk *VK) DefaultHandler(method string, sliceParams ...Params) (Response, er
243250
return response, err
244251
}
245252

253+
acceptEncoding := "gzip"
254+
if vk.zstd {
255+
acceptEncoding = "zstd"
256+
}
257+
246258
req.Header.Set("User-Agent", vk.UserAgent)
247259
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
248260

261+
req.Header.Set("Accept-Encoding", acceptEncoding)
262+
263+
var reader io.Reader
264+
249265
resp, err := vk.Client.Do(req)
250266
if err != nil {
251267
return response, err
252268
}
253269

254-
mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
255-
if mediatype != "application/json" {
256-
_ = resp.Body.Close()
257-
return response, &InvalidContentType{mediatype}
270+
switch resp.Header.Get("Content-Encoding") {
271+
case "zstd":
272+
reader, _ = zstd.NewReader(resp.Body)
273+
case "gzip":
274+
reader, _ = gzip.NewReader(resp.Body)
275+
default:
276+
reader = resp.Body
258277
}
259278

260-
err = json.NewDecoder(resp.Body).Decode(&response)
261-
if err != nil {
279+
mediatype, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
280+
switch mediatype {
281+
case "application/json":
282+
err = json.NewDecoder(reader).Decode(&response)
283+
if err != nil {
284+
_ = resp.Body.Close()
285+
return response, err
286+
}
287+
case "application/x-msgpack":
288+
dec := msgpack.NewDecoder(reader)
289+
dec.SetCustomStructTag("json")
290+
291+
err = dec.Decode(&response)
292+
if err != nil {
293+
_ = resp.Body.Close()
294+
return response, err
295+
}
296+
default:
262297
_ = resp.Body.Close()
263-
return response, err
298+
return response, &InvalidContentType{mediatype}
264299
}
265300

266301
_ = resp.Body.Close()
@@ -291,6 +326,10 @@ func (vk *VK) Request(method string, sliceParams ...Params) ([]byte, error) {
291326

292327
sliceParams = append(sliceParams, reqParams)
293328

329+
if vk.msgpack {
330+
method += ".msgpack"
331+
}
332+
294333
resp, err := vk.Handler(method, sliceParams...)
295334

296335
return resp.Response, err
@@ -303,7 +342,32 @@ func (vk *VK) RequestUnmarshal(method string, obj interface{}, sliceParams ...Pa
303342
return err
304343
}
305344

306-
return json.Unmarshal(rawResponse, &obj)
345+
if vk.msgpack {
346+
dec := msgpack.NewDecoder(bytes.NewReader(rawResponse))
347+
dec.SetCustomStructTag("json")
348+
349+
err = dec.Decode(&obj)
350+
} else {
351+
err = json.Unmarshal(rawResponse, &obj)
352+
}
353+
354+
return err
355+
}
356+
357+
// EnableMessagePack enable using MessagePack instead of JSON.
358+
//
359+
// THIS IS EXPERIMENTAL FUNCTION! Broken encoding returned in some methods.
360+
//
361+
// See https://msgpack.org
362+
func (vk *VK) EnableMessagePack() {
363+
vk.msgpack = true
364+
}
365+
366+
// EnableZstd enable using zstd instead of gzip.
367+
//
368+
// This not use dict.
369+
func (vk *VK) EnableZstd() {
370+
vk.zstd = true
307371
}
308372

309373
func fmtReflectValue(value reflect.Value, depth int) string {

api/api_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,23 @@ func TestContext(t *testing.T) {
322322
_, err := vkUser.UsersGet(p)
323323
assert.EqualError(t, err, "Post \"https://api.vk.com/method/users.get\": context deadline exceeded")
324324
}
325+
326+
func TestVK_EnableMessagePack(t *testing.T) {
327+
t.Parallel()
328+
329+
vk := api.NewVK("")
330+
vk.EnableMessagePack()
331+
332+
_, err := vk.UsersGet(nil)
333+
assert.ErrorIs(t, err, api.ErrAuth)
334+
}
335+
336+
func TestVK_EnableZstd(t *testing.T) {
337+
t.Parallel()
338+
339+
vk := api.NewVK("")
340+
vk.EnableZstd()
341+
342+
_, err := vk.UsersGet(nil)
343+
assert.ErrorIs(t, err, api.ErrAuth)
344+
}

0 commit comments

Comments
 (0)