mirror of
https://github.com/elyby/chrly.git
synced 2025-01-11 06:12:09 +05:30
Implemented remote api mojang uuids provider
This commit is contained in:
parent
d27caa4922
commit
1033069211
@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased] - xxxx-xx-xx
|
## [Unreleased] - xxxx-xx-xx
|
||||||
### Added
|
### Added
|
||||||
|
- Added remote mode for Mojang's textures queue.
|
||||||
- New StatsD metrics:
|
- New StatsD metrics:
|
||||||
- Counters:
|
- Counters:
|
||||||
- `ely.skinsystem.{hostname}.app.mojang_textures.usernames.textures_hit`
|
- `ely.skinsystem.{hostname}.app.mojang_textures.usernames.textures_hit`
|
||||||
|
30
cmd/serve.go
30
cmd/serve.go
@ -3,8 +3,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mono83/slf/wd"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
@ -19,6 +21,7 @@ var serveCmd = &cobra.Command{
|
|||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "Starts http handler for the skins system",
|
Short: "Starts http handler for the skins system",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// TODO: this is a mess, need to organize this code somehow to make services initialization more compact
|
||||||
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"), viper.GetString("sentry.dsn"))
|
logger, err := bootstrap.CreateLogger(viper.GetString("statsd.addr"), viper.GetString("sentry.dsn"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
|
log.Fatal(fmt.Printf("Cannot initialize logger: %v", err))
|
||||||
@ -52,15 +55,32 @@ var serveCmd = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uuidsProvider mojangtextures.UuidsProvider
|
||||||
|
preferredUuidsProvider := viper.GetString("mojang_textures.uuids_provider.driver")
|
||||||
|
if preferredUuidsProvider == "remote" {
|
||||||
|
remoteUrl, err := url.Parse(viper.GetString("mojang_textures.uuids_provider.url"))
|
||||||
|
if err != nil {
|
||||||
|
logger.Emergency("Unable to parse remote url :err", wd.ErrParam(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uuidsProvider = &mojangtextures.RemoteApiUuidsProvider{
|
||||||
|
Url: *remoteUrl,
|
||||||
|
Logger: logger,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uuidsProvider = &mojangtextures.BatchUuidsProvider{
|
||||||
|
IterationDelay: time.Duration(viper.GetInt("queue.loop_delay")) * time.Millisecond,
|
||||||
|
IterationSize: viper.GetInt("queue.batch_size"),
|
||||||
|
Logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
texturesStorage := mojangtextures.NewInMemoryTexturesStorage()
|
texturesStorage := mojangtextures.NewInMemoryTexturesStorage()
|
||||||
texturesStorage.Start()
|
texturesStorage.Start()
|
||||||
mojangTexturesProvider := &mojangtextures.Provider{
|
mojangTexturesProvider := &mojangtextures.Provider{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
UuidsProvider: &mojangtextures.BatchUuidsProvider{
|
UuidsProvider: uuidsProvider,
|
||||||
IterationDelay: time.Duration(viper.GetInt("queue.loop_delay")) * time.Millisecond,
|
|
||||||
IterationSize: viper.GetInt("queue.batch_size"),
|
|
||||||
Logger: logger,
|
|
||||||
},
|
|
||||||
TexturesProvider: &mojangtextures.MojangApiTexturesProvider{
|
TexturesProvider: &mojangtextures.MojangApiTexturesProvider{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
},
|
},
|
||||||
|
71
mojangtextures/remote_api_uuids_provider.go
Normal file
71
mojangtextures/remote_api_uuids_provider.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package mojangtextures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
. "net/url"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mono83/slf/wd"
|
||||||
|
|
||||||
|
"github.com/elyby/chrly/api/mojang"
|
||||||
|
"github.com/elyby/chrly/bootstrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var HttpClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
MaxIdleConnsPerHost: 1024,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteApiUuidsProvider struct {
|
||||||
|
Url URL
|
||||||
|
Logger wd.Watchdog
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RemoteApiUuidsProvider) GetUuid(username string) (*mojang.ProfileInfo, error) {
|
||||||
|
ctx.Logger.IncCounter("mojang_textures.usernames.request", 1)
|
||||||
|
|
||||||
|
url := ctx.Url
|
||||||
|
url.Path = path.Join(url.Path, username)
|
||||||
|
|
||||||
|
request, _ := http.NewRequest("GET", url.String(), nil)
|
||||||
|
request.Header.Add("Accept", "application/json")
|
||||||
|
// Change default User-Agent to allow specify "Username -> UUID at time" Mojang's api endpoint
|
||||||
|
request.Header.Add("User-Agent", "Chrly/"+bootstrap.GetVersion())
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
response, err := HttpClient.Do(request)
|
||||||
|
ctx.Logger.RecordTimer("mojang_textures.usernames.request_time", time.Since(start))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode == 204 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
return nil, &UnexpectedRemoteApiResponse{response}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *mojang.ProfileInfo
|
||||||
|
body, _ := ioutil.ReadAll(response.Body)
|
||||||
|
err = json.Unmarshal(body, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnexpectedRemoteApiResponse struct {
|
||||||
|
Response *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnexpectedRemoteApiResponse) Error() string {
|
||||||
|
return "Unexpected remote api response"
|
||||||
|
}
|
149
mojangtextures/remote_api_uuids_provider_test.go
Normal file
149
mojangtextures/remote_api_uuids_provider_test.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package mojangtextures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/h2non/gock"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
mocks "github.com/elyby/chrly/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
type remoteApiUuidsProviderTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
Provider *RemoteApiUuidsProvider
|
||||||
|
Logger *mocks.WdMock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) SetupSuite() {
|
||||||
|
client := &http.Client{}
|
||||||
|
gock.InterceptClient(client)
|
||||||
|
|
||||||
|
HttpClient = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) SetupTest() {
|
||||||
|
suite.Logger = &mocks.WdMock{}
|
||||||
|
suite.Provider = &RemoteApiUuidsProvider{
|
||||||
|
Logger: suite.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) TearDownTest() {
|
||||||
|
suite.Logger.AssertExpectations(suite.T())
|
||||||
|
gock.Off()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoteApiUuidsProvider(t *testing.T) {
|
||||||
|
suite.Run(t, new(remoteApiUuidsProviderTestSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) TestGetUuidForValidUsername() {
|
||||||
|
suite.Logger.On("IncCounter", "mojang_textures.usernames.request", int64(1)).Once()
|
||||||
|
suite.Logger.On("RecordTimer", "mojang_textures.usernames.request_time", mock.Anything).Once()
|
||||||
|
|
||||||
|
gock.New("http://example.com").
|
||||||
|
Get("/subpath/username").
|
||||||
|
Reply(200).
|
||||||
|
JSON(map[string]interface{}{
|
||||||
|
"id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"name": "username",
|
||||||
|
})
|
||||||
|
|
||||||
|
suite.Provider.Url = shouldParseUrl("http://example.com/subpath")
|
||||||
|
result, err := suite.Provider.GetUuid("username")
|
||||||
|
|
||||||
|
assert := suite.Assert()
|
||||||
|
if assert.NoError(err) {
|
||||||
|
assert.Equal("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", result.Id)
|
||||||
|
assert.Equal("username", result.Name)
|
||||||
|
assert.False(result.IsLegacy)
|
||||||
|
assert.False(result.IsDemo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) TestGetUuidForNotExistsUsername() {
|
||||||
|
suite.Logger.On("IncCounter", "mojang_textures.usernames.request", int64(1)).Once()
|
||||||
|
suite.Logger.On("RecordTimer", "mojang_textures.usernames.request_time", mock.Anything).Once()
|
||||||
|
|
||||||
|
gock.New("http://example.com").
|
||||||
|
Get("/subpath/username").
|
||||||
|
Reply(204)
|
||||||
|
|
||||||
|
suite.Provider.Url = shouldParseUrl("http://example.com/subpath")
|
||||||
|
result, err := suite.Provider.GetUuid("username")
|
||||||
|
|
||||||
|
assert := suite.Assert()
|
||||||
|
assert.Nil(result)
|
||||||
|
assert.Nil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) TestGetUuidForNon20xResponse() {
|
||||||
|
suite.Logger.On("IncCounter", "mojang_textures.usernames.request", int64(1)).Once()
|
||||||
|
suite.Logger.On("RecordTimer", "mojang_textures.usernames.request_time", mock.Anything).Once()
|
||||||
|
|
||||||
|
gock.New("http://example.com").
|
||||||
|
Get("/subpath/username").
|
||||||
|
Reply(504).
|
||||||
|
BodyString("504 Gateway Timeout")
|
||||||
|
|
||||||
|
suite.Provider.Url = shouldParseUrl("http://example.com/subpath")
|
||||||
|
result, err := suite.Provider.GetUuid("username")
|
||||||
|
|
||||||
|
assert := suite.Assert()
|
||||||
|
assert.Nil(result)
|
||||||
|
assert.EqualError(err, "Unexpected remote api response")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) TestGetUuidForNotSuccessRequest() {
|
||||||
|
suite.Logger.On("IncCounter", "mojang_textures.usernames.request", int64(1)).Once()
|
||||||
|
suite.Logger.On("RecordTimer", "mojang_textures.usernames.request_time", mock.Anything).Once()
|
||||||
|
|
||||||
|
expectedError := &net.OpError{Op: "dial"}
|
||||||
|
|
||||||
|
gock.New("http://example.com").
|
||||||
|
Get("/subpath/username").
|
||||||
|
ReplyError(expectedError)
|
||||||
|
|
||||||
|
suite.Provider.Url = shouldParseUrl("http://example.com/subpath")
|
||||||
|
result, err := suite.Provider.GetUuid("username")
|
||||||
|
|
||||||
|
assert := suite.Assert()
|
||||||
|
assert.Nil(result)
|
||||||
|
if assert.Error(err) {
|
||||||
|
assert.IsType(&url.Error{}, err)
|
||||||
|
casterErr, _ := err.(*url.Error)
|
||||||
|
assert.Equal(expectedError, casterErr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *remoteApiUuidsProviderTestSuite) TestGetUuidForInvalidSuccessResponse() {
|
||||||
|
suite.Logger.On("IncCounter", "mojang_textures.usernames.request", int64(1)).Once()
|
||||||
|
suite.Logger.On("RecordTimer", "mojang_textures.usernames.request_time", mock.Anything).Once()
|
||||||
|
|
||||||
|
gock.New("http://example.com").
|
||||||
|
Get("/subpath/username").
|
||||||
|
Reply(200).
|
||||||
|
BodyString("completely not json")
|
||||||
|
|
||||||
|
suite.Provider.Url = shouldParseUrl("http://example.com/subpath")
|
||||||
|
result, err := suite.Provider.GetUuid("username")
|
||||||
|
|
||||||
|
assert := suite.Assert()
|
||||||
|
assert.Nil(result)
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldParseUrl(rawUrl string) url.URL {
|
||||||
|
url, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return *url
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user