Перенесена имплементация на Go, описано Docker compose окружение

This commit is contained in:
ErickSkrauch 2016-07-04 00:20:41 +03:00
parent b38b78bd1e
commit baa1cd3010
9 changed files with 273 additions and 40 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
# IDEA
/.idea

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM golang:1.7
RUN mkdir -p /go/src/app
WORKDIR /go/src/app
COPY ./src /go/src/app
RUN go-wrapper download
RUN go-wrapper install
EXPOSE 80
VOLUME ["/go/src/app"]
CMD ["go-wrapper", "run"]

2
data/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

20
docker-compose.yml Normal file
View File

@ -0,0 +1,20 @@
version: '2'
services:
app:
build: .
links:
- redis
web:
build: ./docker/nginx
ports:
- "80:80"
links:
- app
volumes_from:
- app
redis:
image: redis:3.0
volumes:
- ./data/:/data

8
docker/nginx/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM nginx:1.11
COPY nginx.conf /etc/nginx/nginx.conf
COPY skinsystem.ely.by.conf /etc/nginx/conf.d/default.conf
RUN mkdir -p /data/cache \
/data/logs \
&& chown nginx:nginx -R /data

31
docker/nginx/nginx.conf Normal file
View File

@ -0,0 +1,31 @@
user nginx;
worker_processes auto;
events {
worker_connections 4048;
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 15;
types_hash_max_size 2048;
server_tokens off;
access_log /data/logs/access.log combined buffer=32k;
error_log /data/logs/error.log crit;
gzip off;
client_max_body_size 1M;
proxy_cache_path /data/cache levels=1:2 keys_zone=cache:30m max_size=1G;
include /etc/nginx/conf.d/*.conf;
}

View File

@ -0,0 +1,63 @@
server {
listen 80;
charset utf-8;
set $root_path '/go/src/app';
root $root_path;
proxy_cache_use_stale error timeout invalid_header http_502;
proxy_cache_min_uses 1;
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache cache;
proxy_cache_valid 30m;
}
location /minecraft.php {
if ($arg_name = "") {
return 400;
}
if ($arg_type = "cloack") {
rewrite .* http://skins.minecraft.net/MinecraftCloaks/$arg_name.png? permanent;
break;
}
if ($arg_type = "skin") {
rewrite .* /skins/$arg_name last;
break;
}
return 404;
}
location /cloaks/ {
try_files $uri $uri.png @cloaks;
}
location @cloaks {
rewrite ^/cloaks/(.+?)(\.[^.]*$|$)$ http://skins.minecraft.net/MinecraftCloaks/$1.png? permanent;
}
location ~* ^/skins/$ {
if ($arg_name = "") {
return 400;
}
rewrite .* /skins/$arg_name last;
}
location ~* ^/cloaks/$ {
if ($arg_name = "") {
return 400;
}
rewrite .* /cloaks/$arg_name last;
}
}

View File

@ -1,40 +0,0 @@
location /minecraft.php {
if ($arg_name = "") {
return 400;
}
if ($arg_type = "cloack") {
rewrite .* http://skins.minecraft.net/MinecraftCloaks/$arg_name.png? permanent;
break;
}
if ($arg_type = "skin") {
rewrite .* /skins/$arg_name last;
}
return 404;
}
location /cloaks/ {
try_files $uri $uri.png @cloaks;
}
location @cloaks {
rewrite ^/cloaks/(.+?)(\.[^.]*$|$)$ http://skins.minecraft.net/MinecraftCloaks/$1.png? permanent;
}
location ~* ^/skins/$ {
if ($arg_name = "") {
return 400;
}
rewrite .* /skins/$arg_name permanent;
}
location ~* ^/cloaks/$ {
if ($arg_name = "") {
return 400;
}
rewrite .* /cloaks/$arg_name permanent;
}

133
src/main.go Normal file
View File

@ -0,0 +1,133 @@
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/mediocregopher/radix.v2/redis"
"encoding/json"
"strings"
"time"
"strconv"
"crypto/md5"
"encoding/hex"
)
var client, redisErr = redis.Dial("tcp", "redis:6379")
func main() {
if redisErr != nil {
log.Fatal("Redis unavailable")
}
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/skins/{username}", GetSkin)
router.HandleFunc("/textures/{username}", GetTextures)
log.Fatal(http.ListenAndServe(":80", router))
}
func GetSkin(w http.ResponseWriter, r *http.Request) {
username := ParseUsername(mux.Vars(r)["username"])
log.Println("request skin for username " + username);
rec, err := FindRecord(username)
if (err != nil) {
http.Redirect(w, r, "http://skins.minecraft.net/MinecraftSkins/" + username + ".png", 301)
log.Println("Cannot get skin for username " + username)
return
}
http.Redirect(w, r, rec.Url, 301);
}
func GetTextures(w http.ResponseWriter, r *http.Request) {
username := ParseUsername(mux.Vars(r)["username"])
log.Println("request textures for username " + username)
rec, err := FindRecord(username)
if (err != nil || rec.SkinId == 0) {
rec.Url = "http://skins.minecraft.net/MinecraftSkins/" + username + ".png"
rec.Hash = string(BuildNonElyTexturesHash(username))
}
textures := TexturesResponse{
Skin: &Skin{
Url: rec.Url,
Hash: rec.Hash,
},
}
if (rec.IsSlim) {
textures.Skin.Metadata = &SkinMetadata{
Model: "slim",
}
}
response,_ := json.Marshal(textures)
w.Header().Set("Content-Type", "application/json")
w.Write(response)
}
// STRUCTURES
type SkinItem struct {
UserId int `json:"userId"`
Nickname string `json:"nickname"`
SkinId int `json:"skinId"`
Url string `json:"url"`
Is1_8 bool `json:"is1_8"`
IsSlim bool `json:"isSlim"`
Hash string `json:"hash"`
}
type TexturesResponse struct {
Skin *Skin `json:"SKIN"`
}
type Skin struct {
Url string `json:"url"`
Hash string `json:"hash"`
Metadata *SkinMetadata `json:"metadata,omitempty"`
}
type SkinMetadata struct {
Model string `json:"model"`
}
// TOOLS
func ParseUsername(username string) string {
const suffix = ".png"
if strings.HasSuffix(username, suffix) {
username = strings.TrimSuffix(username, suffix)
}
return username
}
func BuildNonElyTexturesHash(username string) string {
n := time.Now()
hour := time.Date(n.Year(), n.Month(), n.Day(), n.Hour(), 0, 0, 0, time.UTC).Unix()
hasher := md5.New()
hasher.Write([]byte("non-ely-" + strconv.FormatInt(hour, 10) + "-" + username))
return hex.EncodeToString(hasher.Sum(nil))
}
func FindRecord(username string) (SkinItem, error) {
var record SkinItem;
result, err := client.Cmd("GET", BuildKey(username)).Str();
if (err == nil) {
decodeErr := json.Unmarshal([]byte(result), &record)
if (decodeErr != nil) {
log.Println("Cannot decode record data")
}
}
return record, err
}
func BuildKey(username string) string {
return "username:" + strings.ToLower(username)
}