GithubHelp home page GithubHelp logo

elyby / chrly Goto Github PK

View Code? Open in Web Editor NEW
30.0 4.0 4.0 629 KB

Lightweight implementation of Minecraft skins system server. It's packaged and distributed as a Docker image.

License: Apache License 2.0

Go 99.69% Shell 0.09% Dockerfile 0.23%
minecraft go service minecraft-skins skins cape mojang mojang-skins

chrly's People

Contributors

erickskrauch avatar kolfoxy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

chrly's Issues

Allow to store profile information without textures

Right now it's not possible to remove skin without removing a whole user profile. But we should respond with a profile with empty textures when the profile at least exists. We can implement it there, but in the case when the profile has been requested with signed textures, it will be not possible to sign them in place.

So the solution will be to allow the upload of user information without skin and correctly handle it when forming responses.

Restore Mojang skins proxy

Since Mojang disabled skins.minecraft.net API, Chrly doesn't proxy skins if skin not found in data storage.


We investigated current rate limits for Mojang API. From documentation we know that all public APIs are rate limited and currently set at 600 requests per 10 minutes. And it's true for the biggest part of endpoints, but not for UUID -> Profile + Skin/Cape endpoint. For this point there such note as "This has a much stricter rate limit: You can request the same profile once per minute, however you can send as many unique requests as you like". And it's confirmed: we easily performed 19k requests from the one IP for about a minute (Mojang, if it affects you, we are really sorry. You know, it's all for science!).


So here is our new solution.

Chrly must organize some processing queue. In this queue Chrly will send request to Playernames -> UUIDs endpoint to exchange usernames to actual uuids. These requests shouldn't be triggered more than once per second and contain more than 100 nicknames. After uuids received, we can exchange them to textures via UUID -> Profile + Skin/Cape endpoint. This request can be performed without any queue: just as fast as possible.

Data obviously should be cached, but there appear some questions:

  • Should we cache username -> textures and always respond it or should we cache username -> uuid and request textures every time to immediately handle textures changes? It's important, 'cause if we will have rps over 100 (at daytime (UTC+3:00) Ely.by have a way more rps), than we will be unable to check all textures and some users will don't have skins.

  • Another question is should we use primary storage (Redis) or cache it in the Chrly memory with fixed size and some sort of strategy of repression? Currently, Redis eats about 1.5Gb of memory on our production server and is not going to reduce appetites.

  • Third question is how long we should cache it? Should it be hour or a few days?


If someone has any ideas about this stuff, I'd like to hear your opinion.

Change StatsD prefix to "chrly"

Now it usesely.skinsystem.{hostname}.app., but since this is a separate independent project, there should be no Ely.by mentioning.

Avatar by UUID

Is there any way to get the avatar (head 2d image) of a player's skin by their username or uuid on png from the ely.by skinsystem database?

Like for example this link gives me this image:
Notch

Update dependencies, fix dep warnings

~/g/s/g/e/chrly> dep status
dep: WARNING: branch, version, revision, or source should be provided for "github.com/mediocregopher/radix.v2"
dep: WARNING: branch, version, revision, or source should be provided for "github.com/mono83/slf"
dep: WARNING: branch, version, revision, or source should be provided for "github.com/spf13/viper"
dep: WARNING: branch, version, revision, or source should be provided for "github.com/getsentry/raven-go"

Implement worker mode for the textures queue

image

Due to the decrease of the limit on the number of nicknames for which you can get uuid from 100 to 10, as well as the decrease in requests rate limit from 600 to ~240 per 10 minutes (gained experimentally), we can't now process all incoming traffic from one server.

During the initial development we had some doubts that we would be able to process the whole load from 1 server, so we thought over the options to solve this problem. There are 2 ideas:

  1. Connect additional IPs to the server.
  2. Use as a queue some remote executors.

The first option can work in case of placement on a dedicated server with an additional network card, but it's not suitable for some minimal hardware, which we focus on.

The second option is much more viable, because it allows us to share the load on many cheap servers, each of which will work from its own IP address.

The easiest implementation involves launching a web server that will send all incoming requests to the queue and return them as they are returned by the Mojang API. The master node will receive a config parameter that defines the list of IP addresses of the workers to which requests will be sent according to a round-robin algorithm.

But even in the simplest implementation there is a question of the need to ensure the work of the cluster because some of the workers may go down and will have to be temporarily removed from the pool of workers. This task can be solved on our side (which requires additional development) or using some middleware (for example, HAProxy). There is no final solution yet.

Using the skin from SkinsRestorer, if there's no skin on Ely.by

Currently Ely.by SkinSystem doesn't work correctly with Mojang skins.
When skin is not found, it returns URL for the old version of Mojang API, such as "skins.minecraft.net/MinecraftSkins/%player%.png".

Most of offline-mode servers uses plugins for restoring Mojang skins: SkinsRestorer, Custom Skins Manager etc. They make request to Mojang API and get original skin URL, using network and CPU resources of dedicated server.

Now Ely.by authlib has the following algorithm (will describe partially simplifed):

  • If profile recievied from server is empty:
    Library requests Ely.by skin from chrly. If there's no skin with such name in Ely.by database, client use broken Mojang link.
    In this case it's normal behavior, 'cause we can't now get skin from Mojang, if server didn't do that itself (#11).
  • If recievied profile isn't empty (Profile name is different from player name)
    Library uses textures, provided by a server (skin restoring plugin). It allows to use something like "/skin set ".
  • If recievied profile isn't empty (Profile name is equals to player name)
    In this situation, library requests skin from Ely.by. And if it's not found, client uses broken Mojang URL, received from chrly.

Since Minecraft launcher "TL" uses Ely.by skin system by default, all the players have broken Mojang skins on offline-mode servers even with installed skin restoring plugin.

Here we can see logs of Minecraft client:
https://hastebin.brikster.ru/mocowewato.shell

My player with nickname Brikster has Mojang skin and it was restored by the server. Client got correct Mojang link with my skin texture, but finally chose broken Mojang link, that was returned by Ely.by's chrly.

Possible solution (until #11 will be completely solved):
In the last case (when profile, received by client, isn't empty, and player name is equals to name of profile), we can request Ely.by skin from chrly. Then, if chrly replied, that skin is not found, we shouldn't use broken link to a Mojang skin. Instead of that we can use textures, that was provided by the server.

Username to UUID hash entry should be invalidated if UUID no more exists

Sentry Issue: CHRLY-D

Stacktrace (most recent call first):

  File "/home/travis/.gimme/versions/go1.14.linux.amd64/src/reflect/value.go", line 460, in call
  File "/home/travis/.gimme/versions/go1.14.linux.amd64/src/reflect/value.go", line 321, in Call
  File "/home/travis/gopath/src/github.com/elyby/chrly/vendor/github.com/asaskevich/EventBus/event_bus.go", line 158, in doPublish
  File "/home/travis/gopath/src/github.com/elyby/chrly/vendor/github.com/asaskevich/EventBus/event_bus.go", line 144, in Publish
  File "/home/travis/gopath/src/github.com/elyby/chrly/dispatcher/dispatcher.go", line 27, in Emit
  File "/home/travis/gopath/src/github.com/elyby/chrly/mojangtextures/mojang_textures.go", line 196, in getTextures
  File "/home/travis/gopath/src/github.com/elyby/chrly/mojangtextures/mojang_textures.go", line 157, in getResult
  File "/home/travis/gopath/src/github.com/elyby/chrly/mojangtextures/mojang_textures.go", line 132, in getResultAndBroadcast

textures: Unexpected Mojang response error: 200: Empty Response

Potential import collision: import path should be "gopkg.in/h2non/gock.v1", not "github.com/h2non/gock".

Background

The h2non/gock has already renamed it’s import path from "github.com/h2non/gock" to "gopkg.in/h2non/gock.v1".
As README of h2non/gock v1.0.14 said, downstream repos should use "gopkg.in/h2non/gock.v1" to get or import h2non/gock.

Installation
> go get -u gopkg.in/h2non/gock.v1

Examples
See examples directory for more featured use cases.
Simple mocking via tests
package test
import (
  "github.com/nbio/st"
  "gopkg.in/h2non/gock.v1"
  "io/ioutil"
  "net/http"
  "testing"
)
…

But elyby/chrly still used the old path:
https://github.com/elyby/chrly/blob/master/Gopkg.lock#L92

[[projects]]
  digest = "1:5eeb4bfc6db411dbb34a6d9e5d49a9956b160d59fd004ee8f03fe53c9605c082"
  name = "github.com/h2non/gock"
  packages = ["."]
  pruneopts = ""
  revision = "ba88c4862a27596539531ce469478a91bc5a0511"
  version = "v1.0.14"

When you use the old path "github.com/h2non/gock" to import the h2non/gock, it will be very easy to reintroduce h2non/gock through the import statements "import gopkg.in/h2non/gock.v1" in the go source file of h2non/gock.
https://github.com/h2non/gock/blob/v1.0.14/_examples/custom_matcher/matcher.go#L5

package main
import (
	"fmt"
	"gopkg.in/h2non/gock.v1"
	"net/http"
)
…

The "gopkg.in/h2non/gock.v1" and "github.com/h2non/gock" are the same repos. This will work in isolation, bring about potential risks and problems.

Solution

Replace all the old import paths, change "github.com/h2non/gock" to "gopkg.in/h2non/gock.v1".
Where did you import it: https://github.com/elyby/chrly/search?q=github.com%2Fh2non%2Fgock&unscoped_q=github.com%2Fh2non%2Fgock

Add a new strategy for batch uuids queue which will perform request as soon as batch size limit will be filled

This strategy will allow for a faster return of results in case of load bursts. The idea is as follows: the strategy will send a request as soon as there are n elements in the queue or until t time has passed since the first element appeared for processing.

Explaining for humans: people from the CIS should easily understand this logic by drawing an analogy with the drivers of the route: until a full car is filled, it will not go 😅

Corrupted value in the hash:mojang-username-to-uuid

Sentry Issue: CHRLY-H

*errors.errorString: runtime error: index out of range [1] with length 1
  File "/home/travis/.gimme/versions/go1.14.linux.amd64/src/runtime/panic.go", line 88, in goPanicIndex
  File "/home/travis/gopath/src/github.com/elyby/chrly/db/redis/redis.go", line 207, in findMojangUuidByUsername
  File "/home/travis/gopath/src/github.com/elyby/chrly/db/redis/redis.go", line 195, in GetUuid
  File "/home/travis/gopath/src/github.com/elyby/chrly/mojangtextures/storage.go", line 40, in GetUuid
  File "/home/travis/gopath/src/github.com/elyby/chrly/mojangtextures/mojang_textures.go", line 171, in getUuidFromCache
...
(10 additional frame(s) were not displayed)

runtime error: index out of range [1] with length 1

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.