mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-21 23:41:18 +01:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e2a23ccbb | ||
| 41690f84c7 | |||
|
|
d6293cc73d | ||
|
|
c155c8d098 | ||
|
|
bba004acc7 | ||
|
|
9cb32a843f | ||
|
|
e44ebda56f | ||
|
|
fb4d24de6b | ||
|
|
59f27f0769 | ||
|
|
019ca37037 | ||
|
|
56765488e0 | ||
|
|
1328f98746 | ||
|
|
ef4b2f8005 | ||
|
|
fe5ce6b688 | ||
|
|
a6e8e6b0f9 | ||
|
|
29955a1765 | ||
|
|
265a98d404 | ||
|
|
624bf0e338 | ||
|
|
db565f86c8 | ||
|
|
0d2fe02cbc | ||
|
|
e69b3f38fb | ||
|
|
22309efba9 | ||
|
|
3bd76ad918 | ||
|
|
22448c098b | ||
|
|
7ad6f85aec | ||
|
|
b87be6f9f3 | ||
|
|
e0233f2899 | ||
|
|
14cbcae60c | ||
|
|
eae7745758 | ||
|
|
7f95a34e29 | ||
|
|
15a4f17560 | ||
|
|
d967db3ad4 | ||
|
|
d81e2777d2 | ||
|
|
ea1ae64283 | ||
|
|
5eb5b6fa5e | ||
|
|
e6373002a2 | ||
|
|
424a4ab93b | ||
|
|
16948de18d | ||
|
|
8e08e02272 | ||
|
|
c975cc793b |
@ -1,2 +0,0 @@
|
|||||||
https://github.com/mojodna/heroku-buildpack-cairo.git
|
|
||||||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
|
||||||
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.*
|
||||||
|
*.md
|
||||||
|
Dockerfile
|
||||||
|
LICENSE
|
||||||
|
images/
|
||||||
|
node_modules/
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# We use EditorConfig to standardize settings between contributors
|
|
||||||
# See http://editorconfig.org for more info and plugin downloads
|
|
||||||
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = false
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[*.{js, json, yml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
charset = utf-8
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[.gitignore]
|
|
||||||
# echo "filename" >> .gitignorre
|
|
||||||
insert_final_newline = true
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,9 +1,3 @@
|
|||||||
images/*/*.png
|
images/*/*.png
|
||||||
node_modules/
|
node_modules/
|
||||||
coverage/
|
coverage/
|
||||||
.DS_Store
|
|
||||||
*.log
|
|
||||||
*.rdb
|
|
||||||
*.sublime-*
|
|
||||||
config.js
|
|
||||||
lib/public/images/sponsor.png
|
|
||||||
|
|||||||
29
.travis.yml
29
.travis.yml
@ -1,29 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- 8.9.4
|
|
||||||
sudo: false
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- libcairo2-dev
|
|
||||||
- libjpeg8-dev
|
|
||||||
- libpango1.0-dev
|
|
||||||
- libgif-dev
|
|
||||||
- build-essential
|
|
||||||
- g++-4.8
|
|
||||||
script:
|
|
||||||
- npm run-script test-travis
|
|
||||||
notifications:
|
|
||||||
irc:
|
|
||||||
channels:
|
|
||||||
- irc.esper.net#crafatar
|
|
||||||
skip_join: true
|
|
||||||
env:
|
|
||||||
- TRAVIS=true CXX=g++-4.8
|
|
||||||
services:
|
|
||||||
- redis-server
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
52
Dockerfile
52
Dockerfile
@ -1,27 +1,35 @@
|
|||||||
FROM node:12-alpine
|
FROM node:12-alpine AS builder
|
||||||
|
|
||||||
ARG REDIS_URL
|
RUN apk --no-cache add git python3 build-base redis cairo-dev pango-dev jpeg-dev giflib-dev
|
||||||
ARG DEBUG
|
|
||||||
ARG EPHEMERAL_STORAGE
|
|
||||||
|
|
||||||
RUN apk --no-cache --virtual .build-deps add git python build-base
|
RUN adduser -D app
|
||||||
RUN apk --no-cache --virtual .canvas-deps add cairo-dev pango-dev jpeg-dev giflib-dev
|
USER app
|
||||||
|
|
||||||
RUN mkdir -p /crafatar/images/faces
|
|
||||||
RUN mkdir -p /crafatar/images/helms
|
|
||||||
RUN mkdir -p /crafatar/images/skins
|
|
||||||
RUN mkdir -p /crafatar/images/renders
|
|
||||||
RUN mkdir -p /crafatar/images/capes
|
|
||||||
|
|
||||||
VOLUME /crafatar/images
|
|
||||||
|
|
||||||
COPY package.json www.js crafatar/
|
|
||||||
COPY config.example.js crafatar/config.js
|
|
||||||
COPY lib/ crafatar/lib/
|
|
||||||
|
|
||||||
WORKDIR /crafatar
|
|
||||||
|
|
||||||
|
COPY --chown=app package.json package-lock.json /home/app/crafatar/
|
||||||
|
WORKDIR /home/app/crafatar
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
EXPOSE 3000
|
COPY --chown=app . .
|
||||||
ENTRYPOINT npm start
|
RUN mkdir -p images/faces images/helms images/skins images/renders images/capes
|
||||||
|
|
||||||
|
ARG VERBOSE_TEST
|
||||||
|
ARG DEBUG
|
||||||
|
RUN nohup redis-server & npm test
|
||||||
|
|
||||||
|
|
||||||
|
FROM node:12-alpine
|
||||||
|
RUN apk --no-cache add cairo pango jpeg giflib
|
||||||
|
RUN adduser -D app
|
||||||
|
USER app
|
||||||
|
RUN mkdir /home/app/crafatar
|
||||||
|
WORKDIR /home/app/crafatar
|
||||||
|
RUN mkdir -p images/faces images/helms images/skins images/renders images/capes
|
||||||
|
|
||||||
|
COPY --chown=app --from=builder /home/app/crafatar/node_modules/ node_modules/
|
||||||
|
COPY --chown=app package.json www.js config.js ./
|
||||||
|
COPY --chown=app lib/ lib/
|
||||||
|
|
||||||
|
VOLUME /home/app/crafatar/images
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENTRYPOINT ["npm", "start"]
|
||||||
|
EXPOSE 3000
|
||||||
4
LICENSE
4
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Crafatar Team
|
Copyright (c) 2020 Crafatar Team
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
43
README.md
43
README.md
@ -1,8 +1,8 @@
|
|||||||
# Crafatar [](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar)
|
# Crafatar
|
||||||
[](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
|
<img alt="logo" src="lib/public/logo.png" align="right" width="128px" height="128px">
|
||||||
|
|
||||||
|
[](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
|
||||||
|
|
||||||
<img alt="logo" src="lib/public/logo.png" align="right">
|
|
||||||
<a href="https://crafatar.com">Crafatar</a> serves Minecraft avatars based on the skin for use in external applications.
|
<a href="https://crafatar.com">Crafatar</a> serves Minecraft avatars based on the skin for use in external applications.
|
||||||
Inspired by <a href="https://gravatar.com">Gravatar</a> (hence the name) and <a href="https://minotar.net">Minotar</a>.
|
Inspired by <a href="https://gravatar.com">Gravatar</a> (hence the name) and <a href="https://minotar.net">Minotar</a>.
|
||||||
|
|
||||||
@ -34,6 +34,14 @@ Please [visit the website](https://crafatar.com) for details.
|
|||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker network create crafatar
|
||||||
|
docker run --net crafatar -d --name redis redis
|
||||||
|
docker run --net crafatar -v crafatar-images:/home/app/crafatar/images -e REDIS_URL=redis://redis -p 3000:3000 crafatar/crafatar
|
||||||
|
```
|
||||||
|
|
||||||
## Manual
|
## Manual
|
||||||
|
|
||||||
- Install [nodejs](https://nodejs.org/) 12 (LTS)
|
- Install [nodejs](https://nodejs.org/) 12 (LTS)
|
||||||
@ -44,32 +52,21 @@ Please [visit the website](https://crafatar.com) for details.
|
|||||||
|
|
||||||
Crafatar is now available at http://0.0.0.0:3000.
|
Crafatar is now available at http://0.0.0.0:3000.
|
||||||
|
|
||||||
## Docker
|
## Configration / Environment variables
|
||||||
|
|
||||||
Download the docker image from [releases](https://github.com/crafatar/crafatar/releases) (docker hub coming soon™️).
|
See the `config.js` file.
|
||||||
|
|
||||||
```sh
|
# Operational notes
|
||||||
docker load -i crafatar-docker.tar
|
|
||||||
mkdir /path/to/crafatar-images
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
## inodes
|
||||||
docker network create crafatar
|
|
||||||
docker run --net crafatar -d --name redis redis
|
|
||||||
docker run --net crafatar -v /path/to/crafatar-images:/crafatar/images -e REDIS_URL=redis://redis -p 3000:3000 crafatar:2.1.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment variables
|
Crafatar stores a lot of images on disk. For avatars, these are 8×8 px PNG images with an average file size of \~90 bytes. This can lead to issues on file systems such as ext4, which (by default) has a bytes-per-inode ratio of 16Kb. With thousands of files with an average file size below this ratio, you will run out of available inodes before running out of disk space. (Note that this will still be reported as `ENOSPC: no space left on device`).
|
||||||
|
|
||||||
| Variable | Default | Description |
|
Consider using a different file system, changing the inode ratio, or deleting files before the inode limit is reached.
|
||||||
| :- | :- | :- |
|
|
||||||
| `BIND` | `0.0.0.0` | Hostname to listen on |
|
|
||||||
| `PORT` | `3000` | Port to listen on |
|
|
||||||
| `DEBUG` | `false` | Enable verbose debug logging |
|
|
||||||
| `REDIS_URL` | `redis://127.0.0.1:6379` | URI of the redis server |
|
|
||||||
| `EPHEMERAL_STORAGE` | | If set, redis is flushed on start* |
|
|
||||||
|
|
||||||
\* Use this to avoid issues when you have a persistent redis database but an ephemeral storage
|
## disk space and memory usage
|
||||||
|
|
||||||
|
Eventually you will run out of disk space and/or redis will be out of memory. Make sure to delete image files and/or flush redis before this happens.
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
30
app.json
30
app.json
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Crafatar",
|
|
||||||
"description": "A blazing fast API for Minecraft faces!",
|
|
||||||
"repository": "https://github.com/crafatar/crafatar",
|
|
||||||
"keywords": [
|
|
||||||
"node",
|
|
||||||
"minecraft",
|
|
||||||
"avatar",
|
|
||||||
"redis"
|
|
||||||
],
|
|
||||||
"website": "https://crafatar.com/",
|
|
||||||
"env": {
|
|
||||||
"EPHEMERAL_STORAGE": {
|
|
||||||
"description": "Set to true if your storage is gone after deploying",
|
|
||||||
"required": false,
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"addons": [
|
|
||||||
"rediscloud"
|
|
||||||
],
|
|
||||||
"buildpacks": [
|
|
||||||
{
|
|
||||||
"url": "https://github.com/mojodna/heroku-buildpack-cairo.git"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/heroku/heroku-buildpack-nodejs.git"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
var config = {
|
|
||||||
avatars: {
|
|
||||||
min_size: 1, // for avatars
|
|
||||||
max_size: 512, // for avatars; too big values might lead to slow response time or DoS
|
|
||||||
default_size: 160 // for avatars; size to be used when no size given
|
|
||||||
},
|
|
||||||
renders: {
|
|
||||||
min_scale: 1, // for 3D rendered skins
|
|
||||||
max_scale: 10, // for 3D rendered skins; too big values might lead to slow response time or DoS
|
|
||||||
default_scale: 6 // for 3D rendered skins; scale to be used when no scale given
|
|
||||||
},
|
|
||||||
cleaner: {
|
|
||||||
interval: 600, // interval seconds to check limits
|
|
||||||
disk_limit: 524288, // min allowed free KB on disk to trigger image deletion
|
|
||||||
redis_limit: 24576, // max allowed used KB on redis to trigger redis flush
|
|
||||||
amount: 50000 // amount of skins for which all image types are deleted
|
|
||||||
},
|
|
||||||
directories: {
|
|
||||||
faces: "./images/faces/", // directory where faces are kept. must have trailing "/"
|
|
||||||
helms: "./images/helms/", // directory where helms are kept. must have trailing "/"
|
|
||||||
skins: "./images/skins/", // directory where skins are kept. must have trailing "/"
|
|
||||||
renders: "./images/renders/", // directory where rendered skins are kept. must have trailing "/"
|
|
||||||
capes: "./images/capes/" // directory where capes are kept. must have trailing "/"
|
|
||||||
},
|
|
||||||
caching: {
|
|
||||||
local: 1200, // seconds until we will check if user's skin changed. should be > 60 to comply with Mojang's rate limit
|
|
||||||
browser: 3600 // seconds until browser will request image again
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
http_timeout: 2000, // ms until connection to Mojang is dropped
|
|
||||||
debug_enabled: false, // enables logging.debug & editing index page
|
|
||||||
log_time: true // set to false if you use an external logger that provides timestamps
|
|
||||||
},
|
|
||||||
sponsor: {
|
|
||||||
sidebar: '<hr><div class="list-group"><a class="list-group-item sponsor-item" href="https://akliz.net/crafatar" target="_blank" title="Applies to all modpacks and plans for the first billing cycle only.">Save 20% on a Minecraft server with Akliz.</a></div>',
|
|
||||||
top_right: '<a href="https://akliz.net/crafatar" target="_blank" title="Crafatar is sponsored by Akliz" class="sponsor"><img src="/images/sponsor.png" alt="Akliz"></a>'
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
69
config.js
Normal file
69
config.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
var config = {
|
||||||
|
avatars: {
|
||||||
|
// for avatars
|
||||||
|
min_size: parseInt(process.env.AVATAR_MIN) || 1,
|
||||||
|
// for avatars; large values might lead to slow response time or DoS
|
||||||
|
max_size: parseInt(process.env.AVATAR_MAX) || 512,
|
||||||
|
// for avatars; size to be used when no size given
|
||||||
|
default_size: parseInt(process.env.AVATAR_DEFAULT) || 160
|
||||||
|
},
|
||||||
|
renders: {
|
||||||
|
// for 3D rendered skins
|
||||||
|
min_scale: parseInt(process.env.RENDER_MIN) || 1,
|
||||||
|
// for 3D rendered skins; large values might lead to slow response time or DoS
|
||||||
|
max_scale: parseInt(process.env.RENDER_MAX) || 10,
|
||||||
|
// for 3D rendered skins; scale to be used when no scale given
|
||||||
|
default_scale: parseInt(process.env.RENDER_DEFAULT) || 6
|
||||||
|
},
|
||||||
|
directories: {
|
||||||
|
// directory where faces are kept. must have trailing "/"
|
||||||
|
faces: process.env.FACE_DIR || "./images/faces/",
|
||||||
|
// directory where helms are kept. must have trailing "/"
|
||||||
|
helms: process.env.HELM_DIR || "./images/helms/",
|
||||||
|
// directory where skins are kept. must have trailing "/"
|
||||||
|
skins: process.env.SKIN_DIR || "./images/skins/",
|
||||||
|
// directory where rendered skins are kept. must have trailing "/"
|
||||||
|
renders: process.env.RENDER_DIR || "./images/renders/",
|
||||||
|
// directory where capes are kept. must have trailing "/"
|
||||||
|
capes: process.env.CAPE_DIR || "./images/capes/"
|
||||||
|
},
|
||||||
|
caching: {
|
||||||
|
// seconds until we will check if user's skin changed.
|
||||||
|
// Should be > 60 to comply with Mojang's rate limit
|
||||||
|
local: parseInt(process.env.CACHE_LOCAL) || 1200,
|
||||||
|
// seconds until browser will request image again
|
||||||
|
browser: parseInt(process.env.CACHE_BROWSER) || 3600,
|
||||||
|
// If true, redis is flushed on start.
|
||||||
|
// Use this to avoid issues when you have a persistent redis database but an ephemeral storage
|
||||||
|
ephemeral: process.env.EPHEMERAL_STORAGE === "true",
|
||||||
|
// Used for information on the front page
|
||||||
|
cloudflare: process.env.CLOUDFLARE === "true"
|
||||||
|
},
|
||||||
|
// URL of your redis server
|
||||||
|
redis: process.env.REDIS_URL || 'redis://localhost:6379',
|
||||||
|
server: {
|
||||||
|
// port to listen on
|
||||||
|
port: parseInt(process.env.PORT) || 3000,
|
||||||
|
// IP address to listen on
|
||||||
|
bind: process.env.BIND || "0.0.0.0",
|
||||||
|
// ms until connection to Mojang is dropped
|
||||||
|
http_timeout: parseInt(process.env.EXTERNAL_HTTP_TIMEOUT) || 2000,
|
||||||
|
// enables logging.debug & editing index page
|
||||||
|
debug_enabled: process.env.DEBUG === "true",
|
||||||
|
// set to false if you use an external logger that provides timestamps,
|
||||||
|
log_time: process.env.LOG_TIME === "true",
|
||||||
|
// rate limit per second for outgoing requests to the Mojang session server
|
||||||
|
// requests exceeding this limit are skipped and considered failed
|
||||||
|
sessions_rate_limit: parseInt(process.env.SESSIONS_RATE_LIMIT)
|
||||||
|
},
|
||||||
|
sponsor: {
|
||||||
|
sidebar: process.env.SPONSOR_SIDE,
|
||||||
|
top_right: process.env.SPONSOR_TOP_RIGHT
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
textures_url: process.env.TEXTURES_ENDPOINT || "https://textures.minecraft.net/texture/",
|
||||||
|
session_url: process.env.SESSION_ENDPOINT || "https://sessionserver.mojang.com/session/minecraft/profile/"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
43
lib/cache.js
43
lib/cache.js
@ -1,7 +1,6 @@
|
|||||||
var logging = require("./logging");
|
var logging = require("./logging");
|
||||||
var node_redis = require("redis");
|
var node_redis = require("redis");
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
var url = require("url");
|
|
||||||
|
|
||||||
var redis = null;
|
var redis = null;
|
||||||
|
|
||||||
@ -9,19 +8,10 @@ var redis = null;
|
|||||||
// flushes redis when using ephemeral storage (e.g. Heroku)
|
// flushes redis when using ephemeral storage (e.g. Heroku)
|
||||||
function connect_redis() {
|
function connect_redis() {
|
||||||
logging.log("connecting to redis...");
|
logging.log("connecting to redis...");
|
||||||
// parse redis env
|
redis = node_redis.createClient(config.redis);
|
||||||
var redis_env = process.env.REDISCLOUD_URL || process.env.REDIS_URL;
|
|
||||||
var redis_url = redis_env ? url.parse(redis_env) : {};
|
|
||||||
redis_url.port = redis_url.port || 6379;
|
|
||||||
redis_url.hostname = redis_url.hostname || "localhost";
|
|
||||||
// connect to redis
|
|
||||||
redis = node_redis.createClient(redis_url.port, redis_url.hostname);
|
|
||||||
if (redis_url.auth) {
|
|
||||||
redis.auth(redis_url.auth.split(":")[1]);
|
|
||||||
}
|
|
||||||
redis.on("ready", function() {
|
redis.on("ready", function() {
|
||||||
logging.log("Redis connection established.");
|
logging.log("Redis connection established.");
|
||||||
if (process.env.EPHEMERAL_STORAGE) {
|
if (config.caching.ephemeral) {
|
||||||
logging.log("Storage is ephemeral, flushing redis");
|
logging.log("Storage is ephemeral, flushing redis");
|
||||||
redis.flushall();
|
redis.flushall();
|
||||||
}
|
}
|
||||||
@ -41,35 +31,6 @@ exp.get_redis = function() {
|
|||||||
return redis;
|
return redis;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// updates the redis instance's server_info object
|
|
||||||
// callback: error, info object
|
|
||||||
exp.info = function(callback) {
|
|
||||||
redis.info(function(err, res) {
|
|
||||||
// parse the info command and store it in redis.server_info
|
|
||||||
|
|
||||||
// this code block was taken from mranney/node_redis#on_info_cmd
|
|
||||||
// http://git.io/LBUNbg
|
|
||||||
var lines = res.toString().split("\r\n");
|
|
||||||
var obj = {};
|
|
||||||
lines.forEach(function(line) {
|
|
||||||
var parts = line.split(":");
|
|
||||||
if (parts[1]) {
|
|
||||||
obj[parts[0]] = parts[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
obj.versions = [];
|
|
||||||
if (obj.redis_version) {
|
|
||||||
obj.redis_version.split(".").forEach(function(num) {
|
|
||||||
obj.versions.push(+num);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
redis.server_info = obj;
|
|
||||||
|
|
||||||
callback(err, redis.server_info);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// set model type to value of *slim*
|
// set model type to value of *slim*
|
||||||
exp.set_slim = function(rid, userId, slim, callback) {
|
exp.set_slim = function(rid, userId, slim, callback) {
|
||||||
logging.debug(rid, "setting slim for", userId, "to " + slim);
|
logging.debug(rid, "setting slim for", userId, "to " + slim);
|
||||||
|
|||||||
126
lib/cleaner.js
126
lib/cleaner.js
@ -1,126 +0,0 @@
|
|||||||
var logging = require("./logging");
|
|
||||||
var config = require("../config");
|
|
||||||
var cache = require("./cache");
|
|
||||||
var path = require("path");
|
|
||||||
var df = require("node-df");
|
|
||||||
var fs = require("fs");
|
|
||||||
|
|
||||||
var redis = cache.get_redis();
|
|
||||||
var exp = {};
|
|
||||||
|
|
||||||
// does nothing
|
|
||||||
function nil() {}
|
|
||||||
|
|
||||||
// compares redis' used_memory with cleaning_redis_limit
|
|
||||||
// callback: error, true|false
|
|
||||||
function should_clean_redis(callback) {
|
|
||||||
cache.info(function(err, info) {
|
|
||||||
if (err) {
|
|
||||||
callback(err, false);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// logging.debug(info.toString());
|
|
||||||
var used = parseInt(info.used_memory) / 1024;
|
|
||||||
var result = used >= config.cleaner.redis_limit;
|
|
||||||
var msg = "RedisCleaner: " + used + "KB used";
|
|
||||||
(result ? logging.log : logging.debug)(msg);
|
|
||||||
callback(err, result);
|
|
||||||
} catch(e) {
|
|
||||||
callback(e, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// uses `df` to get the available fisk space
|
|
||||||
// callback: error, true|false
|
|
||||||
function should_clean_disk(callback) {
|
|
||||||
df({
|
|
||||||
file: config.directories.faces,
|
|
||||||
prefixMultiplier: "KiB",
|
|
||||||
isDisplayPrefixMultiplier: false,
|
|
||||||
precision: 2
|
|
||||||
}, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
callback(err, false);
|
|
||||||
} else {
|
|
||||||
var available = response[0].available;
|
|
||||||
var result = available < config.cleaner.disk_limit;
|
|
||||||
var msg = "DiskCleaner: " + available + "KB available";
|
|
||||||
(result ? logging.log : logging.debug)(msg);
|
|
||||||
callback(err, result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if redis limit reached, then flush redis
|
|
||||||
// check if disk limit reached, then delete images
|
|
||||||
exp.run = function() {
|
|
||||||
should_clean_redis(function(err, clean) {
|
|
||||||
if (err) {
|
|
||||||
logging.error("Failed to run RedisCleaner");
|
|
||||||
logging.error(err);
|
|
||||||
} else if (clean) {
|
|
||||||
logging.warn("RedisCleaner: Redis limit reached! flushing now");
|
|
||||||
redis.flushall();
|
|
||||||
} else {
|
|
||||||
logging.log("RedisCleaner: Nothing to clean");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
should_clean_disk(function(err, clean) {
|
|
||||||
if (err) {
|
|
||||||
logging.error("Failed to run DiskCleaner");
|
|
||||||
logging.error(err);
|
|
||||||
} else if (clean) {
|
|
||||||
logging.warn("DiskCleaner: Disk limit reached! Cleaning images now");
|
|
||||||
|
|
||||||
// hotfix for #139 | FIXME
|
|
||||||
logging.warn("DiskCleaner: Flushing Redis to prevent ENOENT");
|
|
||||||
redis.flushall();
|
|
||||||
// end hotfix
|
|
||||||
|
|
||||||
var skinsdir = config.directories.skins;
|
|
||||||
var capesdir = config.directories.capes;
|
|
||||||
var facesdir = config.directories.faces;
|
|
||||||
var helmsdir = config.directories.helms;
|
|
||||||
var rendersdir = config.directories.renders;
|
|
||||||
fs.readdir(skinsdir, function(readerr, files) {
|
|
||||||
if (!readerr) {
|
|
||||||
for (var i = 0, l = Math.min(files.length, config.cleaner.amount); i < l; i++) {
|
|
||||||
var filename = files[i];
|
|
||||||
if (filename[0] !== ".") {
|
|
||||||
fs.unlink(path.join(facesdir, filename), nil);
|
|
||||||
fs.unlink(path.join(helmsdir, filename), nil);
|
|
||||||
fs.unlink(path.join(skinsdir, filename), nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fs.readdir(rendersdir, function(readerr, files) {
|
|
||||||
if (!readerr) {
|
|
||||||
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
|
||||||
var filename = files[j];
|
|
||||||
if (filename[0] !== ".") {
|
|
||||||
fs.unlink(rendersdir + filename, nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fs.readdir(capesdir, function(readerr, files) {
|
|
||||||
if (!readerr) {
|
|
||||||
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
|
||||||
var filename = files[j];
|
|
||||||
if (filename[0] !== ".") {
|
|
||||||
fs.unlink(capesdir + filename, nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logging.log("DiskCleaner: Nothing to clean");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = exp;
|
|
||||||
@ -7,8 +7,8 @@ var skins = require("./skins");
|
|||||||
var path = require("path");
|
var path = require("path");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
|
||||||
// 0098cb60-fa8e-427c-b299-793cbd302c9a
|
// 0098cb60fa8e427cb299793cbd302c9a
|
||||||
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
|
var valid_user_id = /^[0-9a-fA-F]{32}$/; // uuid
|
||||||
var hash_pattern = /[0-9a-f]+$/;
|
var hash_pattern = /[0-9a-f]+$/;
|
||||||
|
|
||||||
// gets the hash from the textures.minecraft.net +url+
|
// gets the hash from the textures.minecraft.net +url+
|
||||||
@ -122,13 +122,22 @@ var requests = {
|
|||||||
cape: {}
|
cape: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
function push_request(userId, type, fun) {
|
var loginterval = setInterval(function(){
|
||||||
|
var skinreqs = Object.keys(requests.skin).length;
|
||||||
|
var capereqs = Object.keys(requests.cape).length;
|
||||||
|
if (skinreqs || capereqs) {
|
||||||
|
logging.log("Currently waiting for " + skinreqs + " skin requests and " + capereqs + " cape requests.");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// add a request for +userId+ and +type+ to the queue
|
||||||
|
function push_request(userId, type, callback) {
|
||||||
// avoid special properties (e.g. 'constructor')
|
// avoid special properties (e.g. 'constructor')
|
||||||
var userId_safe = "!" + userId;
|
var userId_safe = "!" + userId;
|
||||||
if (!requests[type][userId_safe]) {
|
if (!requests[type][userId_safe]) {
|
||||||
requests[type][userId_safe] = [];
|
requests[type][userId_safe] = [];
|
||||||
}
|
}
|
||||||
requests[type][userId_safe].push(fun);
|
requests[type][userId_safe].push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls back all queued requests that match userId and type
|
// calls back all queued requests that match userId and type
|
||||||
@ -162,7 +171,6 @@ function store_images(rid, userId, cache_details, type, callback) {
|
|||||||
logging.debug(rid, "adding to request queue");
|
logging.debug(rid, "adding to request queue");
|
||||||
push_request(userId, type, callback);
|
push_request(userId, type, callback);
|
||||||
} else {
|
} else {
|
||||||
// add request to the queue
|
|
||||||
push_request(userId, type, callback);
|
push_request(userId, type, callback);
|
||||||
|
|
||||||
networking.get_profile(rid, userId, function(err, profile) {
|
networking.get_profile(rid, userId, function(err, profile) {
|
||||||
@ -176,7 +184,7 @@ function store_images(rid, userId, cache_details, type, callback) {
|
|||||||
resume(userId, "cape", cache_err, null, false);
|
resume(userId, "cape", cache_err, null, false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// an error occured, not caching. we can try in 60 seconds
|
// an error occured, not caching. we can try again in 60 seconds
|
||||||
resume(userId, type, err, null, false);
|
resume(userId, type, err, null, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -240,10 +248,13 @@ exp.get_image_hash = function(rid, userId, type, callback) {
|
|||||||
}
|
}
|
||||||
store_images(rid, userId, cache_details, type, function(store_err, new_hash, slim) {
|
store_images(rid, userId, cache_details, type, function(store_err, new_hash, slim) {
|
||||||
if (store_err) {
|
if (store_err) {
|
||||||
// we might have a cached hash although an error occured
|
// an error occured, but we have a cached hash
|
||||||
// (e.g. Mojang servers not reachable, using outdated hash)
|
// (e.g. Mojang servers not reachable, using outdated hash)
|
||||||
cache.update_timestamp(rid, userId, true, function(err2) {
|
|
||||||
callback(err2 || store_err, -1, cache_details && cached_hash, slim);
|
// bump the TTL after hitting the rate limit
|
||||||
|
var ratelimited = store_err.code === "RATELIMIT";
|
||||||
|
cache.update_timestamp(rid, userId, !ratelimited, function(err2) {
|
||||||
|
callback(err2 || store_err, 4, cache_details && cached_hash, slim);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var status = cache_details && (cached_hash === new_hash) ? 3 : 2;
|
var status = cache_details && (cached_hash === new_hash) ? 3 : 2;
|
||||||
@ -321,7 +332,7 @@ function get_type(overlay, body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handles creations of 3D renders
|
// handles creations of 3D renders
|
||||||
// callback: error, skin hash, image buffer
|
// callback: error, status, skin hash, image buffer
|
||||||
exp.get_render = function(rid, userId, scale, overlay, body, callback) {
|
exp.get_render = function(rid, userId, scale, overlay, body, callback) {
|
||||||
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
|
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
|
||||||
if (!skin_hash) {
|
if (!skin_hash) {
|
||||||
@ -347,7 +358,7 @@ exp.get_render = function(rid, userId, scale, overlay, body, callback) {
|
|||||||
callback(null, 0, skin_hash, null);
|
callback(null, 0, skin_hash, null);
|
||||||
} else {
|
} else {
|
||||||
fs.writeFile(renderpath, drawn_img, "binary", function(write_err) {
|
fs.writeFile(renderpath, drawn_img, "binary", function(write_err) {
|
||||||
callback(write_err, 2, skin_hash, drawn_img);
|
callback(write_err, status, skin_hash, drawn_img);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -384,4 +395,8 @@ exp.get_cape = function(rid, userId, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exp.stoplog = function() {
|
||||||
|
clearInterval(loginterval);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = exp;
|
module.exports = exp;
|
||||||
@ -23,16 +23,20 @@ function log(level, args, logger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// log with INFO level
|
||||||
exp.log = function() {
|
exp.log = function() {
|
||||||
log(" INFO", arguments);
|
log(" INFO", arguments);
|
||||||
};
|
};
|
||||||
|
// log with WARN level
|
||||||
exp.warn = function() {
|
exp.warn = function() {
|
||||||
log(" WARN", arguments, console.warn);
|
log(" WARN", arguments, console.warn);
|
||||||
};
|
};
|
||||||
|
// log with ERROR level
|
||||||
exp.error = function() {
|
exp.error = function() {
|
||||||
log("ERROR", arguments, console.error);
|
log("ERROR", arguments, console.error);
|
||||||
};
|
};
|
||||||
if (config.server.debug_enabled || process.env.DEBUG === "true") {
|
// log with DEBUG level if debug logging is enabled
|
||||||
|
if (config.server.debug_enabled) {
|
||||||
exp.debug = function() {
|
exp.debug = function() {
|
||||||
log("DEBUG", arguments);
|
log("DEBUG", arguments);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,80 +1,121 @@
|
|||||||
var http_code = require("http").STATUS_CODES;
|
|
||||||
var logging = require("./logging");
|
var logging = require("./logging");
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
var skins = require("./skins");
|
var skins = require("./skins");
|
||||||
|
var http = require("http");
|
||||||
require("./object-patch");
|
require("./object-patch");
|
||||||
|
|
||||||
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
var session_url = config.endpoints.session_url;
|
||||||
var textures_url = "http://textures.minecraft.net/texture/";
|
var textures_url = config.endpoints.textures_url;
|
||||||
|
|
||||||
|
// count requests made to session_url in the last 1000ms
|
||||||
|
var session_requests = [];
|
||||||
|
|
||||||
var exp = {};
|
var exp = {};
|
||||||
|
|
||||||
|
// returns the amount of outgoing session requests made in the last 1000ms
|
||||||
|
function req_count() {
|
||||||
|
var index = session_requests.findIndex((i) => i >= Date.now() - 1000);
|
||||||
|
if (index >= 0) {
|
||||||
|
return session_requests.length - index;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deletes all entries in session_requests, should be called every 1000ms
|
||||||
|
exp.resetCounter = function() {
|
||||||
|
var count = req_count();
|
||||||
|
if (count) {
|
||||||
|
var logfunc = count >= config.server.sessions_rate_limit ? logging.warn : logging.debug;
|
||||||
|
logfunc('Clearing old session requests (count was ' + count + ')');
|
||||||
|
session_requests.splice(0, session_requests.length - count);
|
||||||
|
} else {
|
||||||
|
session_requests = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// performs a GET request to the +url+
|
// performs a GET request to the +url+
|
||||||
// +options+ object includes these options:
|
// +options+ object includes these options:
|
||||||
// encoding (string), default is to return a buffer
|
// encoding (string), default is to return a buffer
|
||||||
// callback: the body, response,
|
// callback: the body, response,
|
||||||
// and error buffer. get_from helper method is available
|
// and error buffer. get_from helper method is available
|
||||||
exp.get_from_options = function(rid, url, options, callback) {
|
exp.get_from_options = function(rid, url, options, callback) {
|
||||||
request.get({
|
var is_session_req = config.server.sessions_rate_limit && url.startsWith(session_url);
|
||||||
url: url,
|
|
||||||
headers: {
|
|
||||||
"User-Agent": "Crafatar (+https://crafatar.com)"
|
|
||||||
},
|
|
||||||
timeout: config.server.http_timeout,
|
|
||||||
followRedirect: false,
|
|
||||||
encoding: options.encoding || null,
|
|
||||||
}, function(error, response, body) {
|
|
||||||
// log url + code + description
|
|
||||||
var code = response && response.statusCode;
|
|
||||||
|
|
||||||
var logfunc = code && code < 405 ? logging.debug : logging.warn;
|
// This is to prevent being blocked by CloudFront for exceeding the rate limit
|
||||||
logfunc(rid, url, code || error && error.code, http_code[code]);
|
if (is_session_req && req_count() >= config.server.sessions_rate_limit) {
|
||||||
|
var e = new Error("Skipped, rate limit exceeded");
|
||||||
// not necessarily used
|
|
||||||
var e = new Error(code);
|
|
||||||
e.name = "HTTP";
|
e.name = "HTTP";
|
||||||
e.code = "HTTPERROR";
|
e.code = "RATELIMIT";
|
||||||
|
|
||||||
switch (code) {
|
var response = new http.IncomingMessage();
|
||||||
case 200:
|
response.statusCode = 403;
|
||||||
case 301:
|
|
||||||
case 302: // never seen, but mojang might use it in future
|
|
||||||
case 307: // never seen, but mojang might use it in future
|
|
||||||
case 308: // never seen, but mojang might use it in future
|
|
||||||
// these are okay
|
|
||||||
break;
|
|
||||||
case 204: // no content, used like 404 by mojang. making sure it really has no content
|
|
||||||
case 404:
|
|
||||||
// can be cached as null
|
|
||||||
body = null;
|
|
||||||
break;
|
|
||||||
case 429: // this shouldn't usually happen, but occasionally does
|
|
||||||
case 500:
|
|
||||||
case 502: // CloudFront can't reach mojang origin
|
|
||||||
case 503:
|
|
||||||
case 504:
|
|
||||||
// we don't want to cache this
|
|
||||||
error = error || e;
|
|
||||||
body = null;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (!error) {
|
|
||||||
// Probably 500 or the likes
|
|
||||||
logging.error(rid, "Unexpected response:", code, body);
|
|
||||||
}
|
|
||||||
error = error || e;
|
|
||||||
body = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body && !body.length) {
|
callback(null, response, e);
|
||||||
// empty response
|
} else {
|
||||||
body = null;
|
is_session_req && session_requests.push(Date.now());
|
||||||
}
|
request.get({
|
||||||
|
url: url,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "Crafatar (+https://crafatar.com)"
|
||||||
|
},
|
||||||
|
timeout: config.server.http_timeout,
|
||||||
|
followRedirect: false,
|
||||||
|
encoding: options.encoding || null,
|
||||||
|
}, function(error, response, body) {
|
||||||
|
// log url + code + description
|
||||||
|
var code = response && response.statusCode;
|
||||||
|
|
||||||
callback(body, response, error);
|
var logfunc = code && (code < 400 || code === 404) ? logging.debug : logging.warn;
|
||||||
});
|
logfunc(rid, url, code || error && error.code, http.STATUS_CODES[code]);
|
||||||
|
|
||||||
|
// not necessarily used
|
||||||
|
var e = new Error(code);
|
||||||
|
e.name = "HTTP";
|
||||||
|
e.code = "HTTPERROR";
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 200:
|
||||||
|
case 301:
|
||||||
|
case 302: // never seen, but mojang might use it in future
|
||||||
|
case 307: // never seen, but mojang might use it in future
|
||||||
|
case 308: // never seen, but mojang might use it in future
|
||||||
|
// these are okay
|
||||||
|
break;
|
||||||
|
case 204: // no content, used like 404 by mojang. making sure it really has no content
|
||||||
|
case 404:
|
||||||
|
// can be cached as null
|
||||||
|
body = null;
|
||||||
|
break;
|
||||||
|
case 403: // Blocked by CloudFront :(
|
||||||
|
case 429: // this shouldn't usually happen, but occasionally does
|
||||||
|
case 500:
|
||||||
|
case 502: // CloudFront can't reach mojang origin
|
||||||
|
case 503:
|
||||||
|
case 504:
|
||||||
|
// we don't want to cache this
|
||||||
|
error = error || e;
|
||||||
|
body = null;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!error) {
|
||||||
|
// Probably 500 or the likes
|
||||||
|
logging.error(rid, "Unexpected response:", code, body);
|
||||||
|
}
|
||||||
|
error = error || e;
|
||||||
|
body = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body && !body.length) {
|
||||||
|
// empty response
|
||||||
|
body = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(body, response, error);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// helper method for get_from_options, no options required
|
// helper method for get_from_options, no options required
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 2.4 KiB |
@ -1,34 +1,64 @@
|
|||||||
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
|
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
var quotes = [
|
||||||
var response = JSON.parse(xhr.responseText);
|
["Crafatar is the best at what it does.", "Shotbow Network", "https://twitter.com/ShotbowNetwork/status/565201303555829762"],
|
||||||
var status = {};
|
["Crafatar seems to stand out from others", "Dabsunter", "https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar"],
|
||||||
response.map(function(elem) {
|
["I can’t tell you how much Crafatar helped me along the way! You guys do some amazing work.", "Luke Chatton", "https://github.com/lukechatton"],
|
||||||
var key = Object.keys(elem)[0];
|
["It's just awesome! Keep up the good work", "Dannyps", "https://forums.spongepowered.org/t/title-cant-be-empty/4964/22"],
|
||||||
status[key] = elem[key];
|
["It's one of the few services that actually does HTTP header caching correctly", "confuser", "https://github.com/BanManagement/BanManager-WebUI/issues/16#issuecomment-73230674"],
|
||||||
});
|
["It's so beautiful. <3", "FerusGrim", "https://twitter.com/FerusGrim/status/642824817683656704"],
|
||||||
|
["Love it! It's great!", "Reddit User", "https://reddit.com/comments/2nth0j/-/cmh5771"],
|
||||||
|
["Such a useful service!", "Tim Z, NameMC", "https://twitter.com/CoderTimZ/status/602682146793349120"],
|
||||||
|
["Thanks for providing us with such a reliable service :)", "BeanBlockz", "https://twitter.com/BeanBlockz/status/743927789422845952"],
|
||||||
|
["This is excellent for my website! Good work.", "cyanide43", "https://reddit.com/comments/2nth0j/-/cmgpq85"],
|
||||||
|
["This is really cool!", "AlexWebber", "https://forums.spongepowered.org/t/crafatar-a-new-minecraft-avatar-service/4964/19"],
|
||||||
|
["This really is looking amazing. Absolutely love it!", "Enter_", "https://forums.spongepowered.org/t/crafatar-a-new-minecraft-avatar-service/4964/21"],
|
||||||
|
["We couldn't believe how flawless your API is, Good job!", "SenceServers", "https://twitter.com/SenceServers/status/697132506626265089"],
|
||||||
|
["WOW, Crafatar is FAST", "Rileriscool", "https://twitter.com/rileriscool/status/562057234986065921"],
|
||||||
|
["You deserve way more popularity", "McSlushie", "https://github.com/crafatar/crafatar/wiki/Credit/a8f37373531b1d2c2cb3557ba809542a2ed81626"],
|
||||||
|
["You do excellent work on Crafatar and are awesome! A very polished, concise & clean project.", "DrCorporate", "https://reddit.com/comments/2r1ns6/-/cnbq5f1"]
|
||||||
|
];
|
||||||
|
// shuffle quotes
|
||||||
|
for (i = quotes.length -1; i > 0; i--) {
|
||||||
|
var a = Math.floor(Math.random() * i);
|
||||||
|
var b = quotes[i];
|
||||||
|
quotes[i] = quotes[a];
|
||||||
|
quotes[a] = b;
|
||||||
|
}
|
||||||
|
|
||||||
var textures_err = status["textures.minecraft.net"] !== "green";
|
var current_quote = 0;
|
||||||
var session_err = status["sessionserver.mojang.com"] !== "green";
|
|
||||||
|
function changeQuote() {
|
||||||
|
var elem = document.querySelector("#quote");
|
||||||
|
var quote = quotes[current_quote];
|
||||||
|
elem.innerHTML = "<b>“" + quote[0] + "”</b><br>― <i>" + quote[1] + "</i>";
|
||||||
|
elem.href = quote[2];
|
||||||
|
current_quote = (current_quote + 1) % quotes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('https://mc-heads.net/json/mc_status').then(r => r.json()).then(data => {
|
||||||
|
var textures_err = data.report.skins.status !== "up";
|
||||||
|
var session_err = data.report.session.status !== "up";
|
||||||
|
|
||||||
if (textures_err || session_err) {
|
if (textures_err || session_err) {
|
||||||
var warn = document.createElement("div");
|
var warn = document.createElement("div");
|
||||||
warn.setAttribute("class", "alert alert-warning");
|
warn.setAttribute("class", "alert alert-warning");
|
||||||
warn.setAttribute("role", "alert");
|
warn.setAttribute("role", "alert");
|
||||||
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect requests at Crafatar. <small><a href=\"https://help.mojang.com\" target=\"_blank\">check status</a>";
|
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect requests at Crafatar. <small><a href=\"https://mc-heads.net/mcstatus\" target=\"_blank\">check status</a>";
|
||||||
document.querySelector("#alerts").appendChild(warn);
|
document.querySelector("#alerts").appendChild(warn);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function(event) {
|
document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
var avatars = document.querySelector("#avatar-wrapper");
|
var avatars = document.querySelector("#avatar-wrapper");
|
||||||
|
// shuffle avatars
|
||||||
for (var i = 0; i < avatars.children.length; i++) {
|
for (var i = 0; i < avatars.children.length; i++) {
|
||||||
// shake 'em on down!
|
|
||||||
// https://stackoverflow.com/a/11972692/2517068
|
|
||||||
avatars.appendChild(avatars.children[Math.random() * i | 0]);
|
avatars.appendChild(avatars.children[Math.random() * i | 0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInterval(changeQuote, 5000);
|
||||||
|
changeQuote();
|
||||||
|
|
||||||
var tryit = document.querySelector("#tryit");
|
var tryit = document.querySelector("#tryit");
|
||||||
var tryname = document.querySelector("#tryname");
|
var tryname = document.querySelector("#tryname");
|
||||||
var images = document.querySelectorAll(".tryit");
|
var images = document.querySelectorAll(".tryit");
|
||||||
@ -44,7 +74,4 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
|||||||
images[j].src = images[j].dataset.src.replace("$", value);
|
images[j].src = images[j].dataset.src.replace("$", value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open("GET", "https://status.mojang.com/check", true);
|
|
||||||
xhr.send();
|
|
||||||
});
|
});
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 8.3 KiB |
@ -35,7 +35,6 @@ a.forkme:hover {
|
|||||||
|
|
||||||
a.sponsor {
|
a.sponsor {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1041;
|
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
@ -43,6 +42,11 @@ a.sponsor {
|
|||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sponsor img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
a.sponsor-item {
|
a.sponsor-item {
|
||||||
color: #aa7100 !important;
|
color: #aa7100 !important;
|
||||||
font-weight: initial;
|
font-weight: initial;
|
||||||
@ -54,6 +58,22 @@ a.sponsor-item {
|
|||||||
background: #fff8ec !important;
|
background: #fff8ec !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#quote-wrapper {
|
||||||
|
line-height: 9.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quote {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: initial;
|
||||||
|
background: #d4e7ff;
|
||||||
|
border-color: #94cbfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quote:hover {
|
||||||
|
background: #dcedff;
|
||||||
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,18 @@ var config = require("../config");
|
|||||||
var crc = require("crc").crc32;
|
var crc = require("crc").crc32;
|
||||||
|
|
||||||
var human_status = {
|
var human_status = {
|
||||||
"-2": "user error", // e.g. invalid size
|
"-2": "user error", // e.g. invalid size
|
||||||
"-1": "server error", // e.g. mojang/network issues
|
"-1": "server error", // e.g. mojang/network issues
|
||||||
0: "none", // cached as null (user has no skin)
|
0: "none", // cached as null (user has no skin)
|
||||||
1: "cached", // found on disk
|
1: "cached", // found on disk
|
||||||
2: "downloaded", // profile downloaded, skin downloaded from mojang servers
|
2: "downloaded", // profile downloaded, skin downloaded from mojang servers
|
||||||
3: "checked", // profile re-downloaded (was too old), has no skin or skin cached
|
3: "checked", // profile re-downloaded (was too old), has no skin or skin cached
|
||||||
|
4: "server error;cached" // tried to check but ran into server error, using cached version
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// print these, but without stacktrace
|
// print these, but without stacktrace
|
||||||
var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR"];
|
var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR", "RATELIMIT"];
|
||||||
|
|
||||||
// handles HTTP responses
|
// handles HTTP responses
|
||||||
// +request+ a http.IncomingMessage
|
// +request+ a http.IncomingMessage
|
||||||
@ -30,16 +31,13 @@ module.exports = function(request, response, result) {
|
|||||||
// These headers are the same for every response
|
// These headers are the same for every response
|
||||||
var headers = {
|
var headers = {
|
||||||
"Content-Type": result.body && result.type || "text/plain",
|
"Content-Type": result.body && result.type || "text/plain",
|
||||||
|
"Content-Length": Buffer.from(result.body || "").length,
|
||||||
"Cache-Control": "max-age=" + config.caching.browser,
|
"Cache-Control": "max-age=" + config.caching.browser,
|
||||||
"Response-Time": Date.now() - request.start,
|
"Response-Time": Date.now() - request.start,
|
||||||
"X-Request-ID": request.id,
|
"X-Request-ID": request.id,
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
};
|
};
|
||||||
|
|
||||||
response.on("close", function() {
|
|
||||||
logging.warn(request.id, "Connection closed");
|
|
||||||
});
|
|
||||||
|
|
||||||
response.on("finish", function() {
|
response.on("finish", function() {
|
||||||
logging.log(request.id, request.method, request.url.href, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
|
logging.log(request.id, request.method, request.url.href, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
|
||||||
});
|
});
|
||||||
@ -87,13 +85,30 @@ module.exports = function(request, response, result) {
|
|||||||
if (result.status === -2) {
|
if (result.status === -2) {
|
||||||
response.writeHead(result.code || 422, headers);
|
response.writeHead(result.code || 422, headers);
|
||||||
} else if (result.status === -1) {
|
} else if (result.status === -1) {
|
||||||
// 500 responses shouldn't be cached
|
// server errors shouldn't be cached
|
||||||
headers["Cache-Control"] = "private, max-age=0, no-cache";
|
headers["Cache-Control"] = "no-cache, max-age=0";
|
||||||
response.writeHead(result.code || 500, headers);
|
if (result.body && result.hash && !result.hash.startsWith("mhf_")) {
|
||||||
|
headers["Warning"] = '110 Crafatar "Response is Stale"'
|
||||||
|
headers["Etag"] = etag;
|
||||||
|
result.code = result.code || 200;
|
||||||
|
}
|
||||||
|
if (result.err && result.err.code === "ENOENT") {
|
||||||
|
result.code = result.code || 500;
|
||||||
|
}
|
||||||
|
if (!result.code) {
|
||||||
|
// Don't use 502 on Cloudflare
|
||||||
|
// As they will show their own error page instead
|
||||||
|
// https://support.cloudflare.com/hc/en-us/articles/200172706
|
||||||
|
result.code = config.caching.cloudflare ? 500 : 502;
|
||||||
|
}
|
||||||
|
response.writeHead(result.code, headers);
|
||||||
} else {
|
} else {
|
||||||
if (result.body) {
|
if (result.body) {
|
||||||
headers.Etag = etag;
|
if (result.status === 4) {
|
||||||
response.writeHead(result.status === 2 ? 201 : 200, headers);
|
headers["Warning"] = '111 Crafatar "Revalidation Failed"'
|
||||||
|
}
|
||||||
|
headers["Etag"] = etag;
|
||||||
|
response.writeHead(200, headers);
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(404, headers);
|
response.writeHead(404, headers);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,12 +14,10 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
|
|||||||
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
||||||
if (helpers.id_valid(def)) {
|
if (helpers.id_valid(def)) {
|
||||||
// clean up the old URL to match new image
|
// clean up the old URL to match new image
|
||||||
var parsed = req.url;
|
req.url.searchParams.delete('default');
|
||||||
delete parsed.query.default;
|
req.url.path_list[1] = def;
|
||||||
delete parsed.search;
|
req.url.pathname = req.url.path_list.join('/');
|
||||||
parsed.path_list[1] = def;
|
var newUrl = req.url.toString();
|
||||||
parsed.pathname = "/" + parsed.path_list.join("/");
|
|
||||||
var newUrl = url.format(parsed);
|
|
||||||
callback({
|
callback({
|
||||||
status: img_status,
|
status: img_status,
|
||||||
redirect: newUrl,
|
redirect: newUrl,
|
||||||
@ -53,9 +51,9 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
|
|||||||
// GET avatar request
|
// GET avatar request
|
||||||
module.exports = function(req, callback) {
|
module.exports = function(req, callback) {
|
||||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||||
var size = parseInt(req.url.query.size) || config.avatars.default_size;
|
var size = parseInt(req.url.searchParams.get("size")) || config.avatars.default_size;
|
||||||
var def = req.url.query.default;
|
var def = req.url.searchParams.get("default");
|
||||||
var overlay = Object.prototype.hasOwnProperty.call(req.url.query, "overlay") || Object.prototype.hasOwnProperty.call(req.url.query, "helm");
|
var overlay = req.url.searchParams.has("overlay") || req.url.searchParams.has("helm");
|
||||||
|
|
||||||
// check for extra paths
|
// check for extra paths
|
||||||
if (req.url.path_list.length > 2) {
|
if (req.url.path_list.length > 2) {
|
||||||
@ -67,6 +65,9 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strip dashes
|
||||||
|
userId = userId.replace(/-/g, "");
|
||||||
|
|
||||||
// Prevent app from crashing/freezing
|
// Prevent app from crashing/freezing
|
||||||
if (size < config.avatars.min_size || size > config.avatars.max_size) {
|
if (size < config.avatars.min_size || size > config.avatars.max_size) {
|
||||||
// "Unprocessable Entity", valid request, but semantically erroneous:
|
// "Unprocessable Entity", valid request, but semantically erroneous:
|
||||||
@ -84,9 +85,6 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip dashes
|
|
||||||
userId = userId.replace(/-/g, "");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_avatar(req.id, userId, overlay, size, function(err, status, image, hash) {
|
helpers.get_avatar(req.id, userId, overlay, size, function(err, status, image, hash) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ var cache = require("../cache");
|
|||||||
// GET cape request
|
// GET cape request
|
||||||
module.exports = function(req, callback) {
|
module.exports = function(req, callback) {
|
||||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||||
var def = req.url.query.default;
|
var def = req.url.searchParams.get('default');
|
||||||
var rid = req.id;
|
var rid = req.id;
|
||||||
|
|
||||||
// check for extra paths
|
// check for extra paths
|
||||||
@ -17,6 +17,8 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strip dashes
|
||||||
|
userId = userId.replace(/-/g, "");
|
||||||
if (!helpers.id_valid(userId)) {
|
if (!helpers.id_valid(userId)) {
|
||||||
callback({
|
callback({
|
||||||
status: -2,
|
status: -2,
|
||||||
@ -25,9 +27,6 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip dashes
|
|
||||||
userId = userId.replace(/-/g, "");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_cape(rid, userId, function(err, hash, status, image) {
|
helpers.get_cape(rid, userId, function(err, hash, status, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ var ejs = require("ejs");
|
|||||||
var str;
|
var str;
|
||||||
var index;
|
var index;
|
||||||
|
|
||||||
|
// pre-compile the index page
|
||||||
function compile() {
|
function compile() {
|
||||||
logging.log("Compiling index page");
|
logging.log("Compiling index page");
|
||||||
str = read(path.join(__dirname, "..", "views", "index.html.ejs"), "utf-8");
|
str = read(path.join(__dirname, "..", "views", "index.html.ejs"), "utf-8");
|
||||||
|
|||||||
@ -17,12 +17,10 @@ function handle_default(rid, scale, overlay, body, img_status, userId, size, def
|
|||||||
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
||||||
if (helpers.id_valid(def)) {
|
if (helpers.id_valid(def)) {
|
||||||
// clean up the old URL to match new image
|
// clean up the old URL to match new image
|
||||||
var parsed = req.url;
|
req.url.searchParams.delete('default');
|
||||||
delete parsed.query.default;
|
req.url.path_list[2] = def;
|
||||||
delete parsed.search;
|
req.url.pathname = req.url.path_list.join('/');
|
||||||
parsed.path_list[2] = def;
|
var newUrl = req.url.toString();
|
||||||
parsed.pathname = "/" + parsed.path_list.join("/");
|
|
||||||
var newUrl = url.format(parsed);
|
|
||||||
callback({
|
callback({
|
||||||
status: img_status,
|
status: img_status,
|
||||||
redirect: newUrl,
|
redirect: newUrl,
|
||||||
@ -62,9 +60,9 @@ module.exports = function(req, callback) {
|
|||||||
var rid = req.id;
|
var rid = req.id;
|
||||||
var body = raw_type === "body";
|
var body = raw_type === "body";
|
||||||
var userId = (req.url.path_list[2] || "").split(".")[0];
|
var userId = (req.url.path_list[2] || "").split(".")[0];
|
||||||
var def = req.url.query.default;
|
var def = req.url.searchParams.get("default");
|
||||||
var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
|
var scale = parseInt(req.url.searchParams.get("scale")) || config.renders.default_scale;
|
||||||
var overlay = Object.prototype.hasOwnProperty.call(req.url.query, "overlay") || Object.prototype.hasOwnProperty.call(req.url.query, "helm");
|
var overlay = req.url.searchParams.has("overlay") || req.url.searchParams.has("helm");
|
||||||
|
|
||||||
// check for extra paths
|
// check for extra paths
|
||||||
if (req.url.path_list.length > 3) {
|
if (req.url.path_list.length > 3) {
|
||||||
@ -85,6 +83,9 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strip dashes
|
||||||
|
userId = userId.replace(/-/g, "");
|
||||||
|
|
||||||
if (scale < config.renders.min_scale || scale > config.renders.max_scale) {
|
if (scale < config.renders.min_scale || scale > config.renders.max_scale) {
|
||||||
callback({
|
callback({
|
||||||
status: -2,
|
status: -2,
|
||||||
@ -99,9 +100,6 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip dashes
|
|
||||||
userId = userId.replace(/-/g, "");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_render(rid, userId, scale, overlay, body, function(err, status, hash, image) {
|
helpers.get_render(rid, userId, scale, overlay, body, function(err, status, hash, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@ -14,12 +14,10 @@ function handle_default(img_status, userId, def, req, err, callback) {
|
|||||||
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
||||||
if (helpers.id_valid(def)) {
|
if (helpers.id_valid(def)) {
|
||||||
// clean up the old URL to match new image
|
// clean up the old URL to match new image
|
||||||
var parsed = req.url;
|
req.url.searchParams.delete('default');
|
||||||
delete parsed.query.default;
|
req.url.path_list[1] = def;
|
||||||
delete parsed.search;
|
req.url.pathname = req.url.path_list.join('/');
|
||||||
parsed.path_list[1] = def;
|
var newUrl = req.url.toString();
|
||||||
parsed.pathname = "/" + parsed.path_list.join("/");
|
|
||||||
var newUrl = url.format(parsed);
|
|
||||||
callback({
|
callback({
|
||||||
status: img_status,
|
status: img_status,
|
||||||
redirect: newUrl,
|
redirect: newUrl,
|
||||||
@ -62,7 +60,7 @@ function handle_default(img_status, userId, def, req, err, callback) {
|
|||||||
// GET skin request
|
// GET skin request
|
||||||
module.exports = function(req, callback) {
|
module.exports = function(req, callback) {
|
||||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||||
var def = req.url.query.default;
|
var def = req.url.searchParams.get("default");
|
||||||
var rid = req.id;
|
var rid = req.id;
|
||||||
|
|
||||||
// check for extra paths
|
// check for extra paths
|
||||||
@ -75,6 +73,8 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strip dashes
|
||||||
|
userId = userId.replace(/-/g, "");
|
||||||
if (!helpers.id_valid(userId)) {
|
if (!helpers.id_valid(userId)) {
|
||||||
callback({
|
callback({
|
||||||
status: -2,
|
status: -2,
|
||||||
@ -83,9 +83,6 @@ module.exports = function(req, callback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip dashes
|
|
||||||
userId = userId.replace(/-/g, "");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_skin(rid, userId, function(err, hash, status, image, slim) {
|
helpers.get_skin(rid, userId, function(err, hash, status, image, slim) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
var querystring = require("querystring");
|
var querystring = require("querystring");
|
||||||
var response = require("./response");
|
var response = require("./response");
|
||||||
|
var helpers = require("./helpers.js");
|
||||||
var toobusy = require("toobusy-js");
|
var toobusy = require("toobusy-js");
|
||||||
var logging = require("./logging");
|
var logging = require("./logging");
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
@ -21,24 +22,33 @@ var routes = {
|
|||||||
|
|
||||||
// serves assets from lib/public
|
// serves assets from lib/public
|
||||||
function asset_request(req, callback) {
|
function asset_request(req, callback) {
|
||||||
var filename = path.join(__dirname, "public", req.url.path_list.join("/"));
|
const filename = path.join(__dirname, "public", ...req.url.path_list);
|
||||||
fs.access(filename, function(fs_err) {
|
const relative = path.relative(path.join(__dirname, "public"), filename);
|
||||||
if (!fs_err) {
|
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
||||||
fs.readFile(filename, function(err, data) {
|
fs.access(filename, function(fs_err) {
|
||||||
callback({
|
if (!fs_err) {
|
||||||
body: data,
|
fs.readFile(filename, function(err, data) {
|
||||||
type: mime.getType(filename),
|
callback({
|
||||||
err: err,
|
body: data,
|
||||||
|
type: mime.getType(filename),
|
||||||
|
err: err,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
callback({
|
||||||
callback({
|
body: "Not found",
|
||||||
body: "Not found",
|
status: -2,
|
||||||
status: -2,
|
code: 404,
|
||||||
code: 404,
|
});
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
} else {
|
||||||
|
callback({
|
||||||
|
body: "Forbidden",
|
||||||
|
status: -2,
|
||||||
|
code: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generates a 12 character random string
|
// generates a 12 character random string
|
||||||
@ -46,26 +56,18 @@ function request_id() {
|
|||||||
return Math.random().toString(36).substring(2, 14);
|
return Math.random().toString(36).substring(2, 14);
|
||||||
}
|
}
|
||||||
|
|
||||||
// splits a URL path into an Array
|
// splits decoded URL path into an Array
|
||||||
// the path is resolved and decoded
|
|
||||||
function path_list(pathname) {
|
function path_list(pathname) {
|
||||||
// remove double and trailing slashes
|
|
||||||
pathname = pathname.replace(/\/\/+/g, "/").replace(/(.)\/$/, "$1");
|
|
||||||
var list = pathname.split("/");
|
var list = pathname.split("/");
|
||||||
list.shift();
|
list.shift();
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
// URL decode
|
|
||||||
list[i] = querystring.unescape(list[i]);
|
|
||||||
}
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handles the +req+ by routing to the request to the appropriate module
|
// handles the +req+ by routing to the request to the appropriate module
|
||||||
function requestHandler(req, res) {
|
function requestHandler(req, res) {
|
||||||
req.url = url.parse(req.url, true);
|
req.url = new URL(decodeURI(req.url), 'http://' + req.headers.host);
|
||||||
req.url.query = req.url.query || {};
|
req.url.pathname = path.resolve('/', req.url.pathname);
|
||||||
req.url.path_list = path_list(req.url.pathname);
|
req.url.path_list = path_list(req.url.pathname);
|
||||||
|
|
||||||
req.id = request_id();
|
req.id = request_id();
|
||||||
req.start = Date.now();
|
req.start = Date.now();
|
||||||
|
|
||||||
@ -135,9 +137,10 @@ function requestHandler(req, res) {
|
|||||||
|
|
||||||
var exp = {};
|
var exp = {};
|
||||||
|
|
||||||
|
// Start the server
|
||||||
exp.boot = function(callback) {
|
exp.boot = function(callback) {
|
||||||
var port = process.env.PORT || 3000;
|
var port = config.server.port;
|
||||||
var bind_ip = process.env.BIND || "0.0.0.0";
|
var bind_ip = config.server.bind;
|
||||||
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
|
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
|
||||||
logging.log("Server running on http://" + bind_ip + ":" + port + "/");
|
logging.log("Server running on http://" + bind_ip + ":" + port + "/");
|
||||||
if (callback) {
|
if (callback) {
|
||||||
@ -149,7 +152,7 @@ exp.boot = function(callback) {
|
|||||||
// wait for established connections to finish (30s max),
|
// wait for established connections to finish (30s max),
|
||||||
// then exit
|
// then exit
|
||||||
process.on("SIGTERM", function() {
|
process.on("SIGTERM", function() {
|
||||||
logging.warn("Got SIGTERM, no longer accepting connections!");
|
logging.warn("Got SIGTERM, no longer accepting new connections!");
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
logging.error("Dropping connections after 30s. Force quit.");
|
logging.error("Dropping connections after 30s. Force quit.");
|
||||||
@ -163,7 +166,9 @@ exp.boot = function(callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Close the server
|
||||||
exp.close = function(callback) {
|
exp.close = function(callback) {
|
||||||
|
helpers.stoplog();
|
||||||
server.close(callback);
|
server.close(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Crafatar – A blazing fast API for Minecraft faces!</title>
|
<title>Crafatar – A blazing fast API for Minecraft faces!</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="icon" sizes="16x16" type="image/png" href="/favicon.png">
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
<link rel="stylesheet" href="/stylesheets/bootstrap.min.css">
|
<link rel="stylesheet" href="/stylesheets/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="/stylesheets/style.css">
|
<link rel="stylesheet" href="/stylesheets/style.css">
|
||||||
<meta name="description" content="A blazing fast API for Minecraft faces with support for avatars, skins, and 3D renders!">
|
<meta name="description" content="A blazing fast API for Minecraft faces with support for avatars, skins, and 3D renders!">
|
||||||
@ -61,11 +61,7 @@
|
|||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<section id="documentation">
|
<section id="documentation">
|
||||||
<div id="alerts">
|
<div id="alerts">
|
||||||
<div class="alert alert-warning" role="alert">
|
|
||||||
<h5>Crafatar is rate limited by Mojang!</h5>
|
|
||||||
Please consider installing your own instance of Crafatar!<br>
|
|
||||||
For more information visit <a href="https://github.com/crafatar/crafatar/issues/260">our issue tracker</a>.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="try">
|
<section id="try">
|
||||||
@ -80,6 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<p>You can use <a rel="nofollow" target="_blank" href="https://minecraftuuid.com">minecraftuuid.com</a> to find the UUID of a username.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="avatars">
|
<section id="avatars">
|
||||||
@ -214,10 +211,12 @@
|
|||||||
<h3><a href="#meta-caching">About Caching</a></h3>
|
<h3><a href="#meta-caching">About Caching</a></h3>
|
||||||
<p>
|
<p>
|
||||||
Crafatar checks for skin updates every <%= config.caching.local / 60 %> minutes.<br>
|
Crafatar checks for skin updates every <%= config.caching.local / 60 %> minutes.<br>
|
||||||
Images are cached in your browser for <%= config.caching.browser / 60 %> minutes until a new request to Crafatar is made.<br>
|
Images are also cached in your browser for <%= config.caching.browser / 60 %> minutes unless you clear your browser cache.
|
||||||
In addition, <span title="A CDN and caching proxy">CloudFlare</span> caches up to 2 hours on a per-url basis.
|
<% if (config.caching.cloudflare) { %>
|
||||||
|
<br>In addition, <span title="A CDN and caching proxy">Cloudflare</span> may cache images as long as your browser would.
|
||||||
|
<% } %>
|
||||||
</p>
|
</p>
|
||||||
<p>When you changed your skin you can try clearing your browser cache to see the change faster.</p>
|
<p>After changing your Minecraft skin, you can try clearing your browser cache to see the change faster.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="meta-cors">
|
<section id="meta-cors">
|
||||||
@ -228,11 +227,30 @@
|
|||||||
<section id="meta-http-headers">
|
<section id="meta-http-headers">
|
||||||
<h3><a href="#meta-http-headers">HTTP Headers</a></h3>
|
<h3><a href="#meta-http-headers">HTTP Headers</a></h3>
|
||||||
<p>
|
<p>
|
||||||
Responses come with some custom HTTP headers, useful for debugging.<br>
|
Crafatar always replies with a <code>200 OK</code> status code when the requested user's skin/cape was found. This is also used in some rare cases when Mojang servers are having issues and the image couldn't be checked for changes, but Crafatar still had a cached version.
|
||||||
Please note that these headers may be cached by <span title="A CDN and caching proxy">CloudFlare</span>.
|
<% if (config.caching.cloudflare) { %>
|
||||||
|
<code>500 Server Error</code> is used when no skin/cape was found because of Mojang or Crafatar server issues.
|
||||||
|
<% } else { %>
|
||||||
|
<code>502 Bad Gateway</code> and <code>500 Server Error</code> are used when no skin/cape was found because of Mojang or Crafatar server issues.
|
||||||
|
<% } %>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Note that requests are usually answered with an image (with Steve/Alex skin), even if an error occured!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Responses come with some HTTP headers that are useful for debugging.
|
||||||
|
<% if (config.caching.cloudflare) { %>
|
||||||
|
<br>Please note that these headers may be cached by <span title="A CDN and caching proxy">Cloudflare</span>.
|
||||||
|
<% } %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Warning</b>: When using a cached image after an error occured. One of:
|
||||||
|
<ul>
|
||||||
|
<li><code>110 Crafatar "Response is Stale"</code></li>
|
||||||
|
<li><code>111 Crafatar "Revalidation Failed"</code></li>
|
||||||
|
</ul>
|
||||||
<li>
|
<li>
|
||||||
<b>X-Storage-Type</b>: Details about how the requested image was stored on the server
|
<b>X-Storage-Type</b>: Details about how the requested image was stored on the server
|
||||||
<ul>
|
<ul>
|
||||||
@ -241,52 +259,56 @@
|
|||||||
<li><b>checked</b>: Requested skin details, skin cached. (1 external request)<br>
|
<li><b>checked</b>: Requested skin details, skin cached. (1 external request)<br>
|
||||||
This happens either when the user removed their skin or when it didn't change.</li>
|
This happens either when the user removed their skin or when it didn't change.</li>
|
||||||
<li><b>downloaded</b>: Requested skin details, skin downloaded. (2 external requests)</li>
|
<li><b>downloaded</b>: Requested skin details, skin downloaded. (2 external requests)</li>
|
||||||
<li><b>server error</b>: This can happen, for example, when Mojang's servers are down.<br>
|
<li><b>server error</b>: This can happen, for example, when Mojang's servers are down.</li>
|
||||||
If possible, a cached image is served instead.</li>
|
<li><b>server error;cached</b>: Same as server error, but a cached skin was available.</li>
|
||||||
<li><b>user error</b>: You have done something wrong, such as requesting a malformed uuid.<br>
|
<li><b>user error</b>: You have done something wrong, such as requesting a malformed uuid.<br>
|
||||||
Check the response body for details.</li>
|
Check the response body for details.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<li>
|
<li>
|
||||||
<b>X-Request-ID</b>: The internal ID assigned to this request.<br>
|
<b>X-Request-ID</b>: The internal ID assigned to this request.<br>
|
||||||
If you think something is wrong with your request, please <a href="#contact">contact us</a> and provide this ID.
|
If you think something is wrong with your request, please contact us and provide this ID.
|
||||||
|
<li>
|
||||||
|
<b>Response-Time</b>: How long it took Crafatar to answer the request, in ms.
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="contact">
|
|
||||||
<h2><a href="#contact">Contact</a></h2>
|
|
||||||
<ul>
|
|
||||||
<li>Follow us on twitter <a href="https://twitter.com/crafatar" target="_blank">@crafatar</a></li>
|
|
||||||
<li>Open an issue <a href="https://github.com/crafatar/crafatar/issues" target="_blank">on GitHub</a></li>
|
|
||||||
<li><a href="https://webchat.esper.net/?channels=crafatar" target="_blank">Join us</a> in <a href="irc://irc.esper.net/crafatar">#crafatar</a> on irc.esper.net</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<h4>Popular Crafatar users</h4>
|
<h4>Popular Crafatar users</h4>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<a rel="nofollow" href="http://technicpack.net" target="_blank" class="list-group-item">Technic</a>
|
|
||||||
<a rel="nofollow" href="https://hypixel.net" target="_blank" class="list-group-item">Hypixel</a>
|
<a rel="nofollow" href="https://hypixel.net" target="_blank" class="list-group-item">Hypixel</a>
|
||||||
<a rel="nofollow" href="https://www.minecraft-index.com" target="_blank" class="list-group-item">MC Index</a>
|
<a rel="nofollow" href="https://mineplex.com" target="_blank" class="list-group-item">Mineplex</a>
|
||||||
<a rel="nofollow" href="https://www.minehq.com/hcteams/leaderboards" target="_blank" class="list-group-item">MineHQ</a>
|
<a rel="nofollow" href="https://hivemc.com" target="_blank" class="list-group-item">The Hive</a>
|
||||||
<a rel="nofollow" href="https://shotbow.net" target="_blank" class="list-group-item">Shotbow</a>
|
<a rel="nofollow" href="https://www.technicpack.net" target="_blank" class="list-group-item">Technic Pack</a>
|
||||||
|
<a rel="nofollow" href="https://namemc.com" target="_blank" class="list-group-item">NameMC</a>
|
||||||
<a rel="nofollow" href="https://mcuuid.net/" target="_blank" class="list-group-item">MCUUID</a>
|
<a rel="nofollow" href="https://mcuuid.net/" target="_blank" class="list-group-item">MCUUID</a>
|
||||||
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F" target="_blank" class="list-group-item">and many more…</a>
|
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F" target="_blank" class="list-group-item">and many more…</a>
|
||||||
</div>
|
</div>
|
||||||
<p>See also: <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">what users say</a> about Crafatar</p>
|
<hr>
|
||||||
|
<h4>Quotes</h4>
|
||||||
|
<div id="quote-wrapper" class="list-group">
|
||||||
|
<a id="quote" rel="nofollow" target="_blank" class="list-group-item"></a>
|
||||||
|
</div>
|
||||||
|
<p>See <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">all quotes</a>.</p>
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Crafatar Tools & Plugins</h4>
|
<h4>Crafatar Tools & Plugins</h4>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
|
<a rel="nofollow" href="https://github.com/DiscordSRV/DiscordSRV#readme" target="_blank" class="list-group-item">DiscordSRV</a>
|
||||||
|
<a rel="nofollow" href="https://github.com/the-obsidian/discourse-minecraft-avatar" target="_blank" class="list-group-item">Discourse Minecraft Avatar</a>
|
||||||
<a rel="nofollow" href="https://xenforo.com/community/resources/associationmc.3232/" target="_blank" class="list-group-item">AssociationMc <i>(XenForo)</i></a>
|
<a rel="nofollow" href="https://xenforo.com/community/resources/associationmc.3232/" target="_blank" class="list-group-item">AssociationMc <i>(XenForo)</i></a>
|
||||||
<a rel="nofollow" href="https://github.com/yeahwhat-mc/discourse-yeahwhat" target="_blank" class="list-group-item">Minecraft Heads <i>(Discourse)</i></a>
|
<a rel="nofollow" href="https://open.vanillaforums.com/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
|
||||||
<a rel="nofollow" href="http://vanillaforums.org/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
|
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Spigot/Bukkit)</i></a>
|
||||||
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Bukkit)</i></a>
|
|
||||||
<a rel="nofollow" href="https://github.com/sk89q/Plumeria" target="_blank" class="list-group-item">Plumeria <i>(Discord)</i></a>
|
|
||||||
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F#other-services-using-crafatar" target="_blank" class="list-group-item">and many more…</a>
|
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F#other-services-using-crafatar" target="_blank" class="list-group-item">and many more…</a>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h4>Contact</h4>
|
||||||
|
<div class="list-group">
|
||||||
|
<a class="list-group-item" href="https://twitter.com/crafatar" target="_blank">@crafatar on Twitter</a>
|
||||||
|
<a class="list-group-item" href="https://github.com/crafatar/crafatar/issues" target="_blank">Issue tracker</a>
|
||||||
|
</div>
|
||||||
<% if (config.sponsor.sidebar) { %>
|
<% if (config.sponsor.sidebar) { %>
|
||||||
|
<hr>
|
||||||
<%- config.sponsor.sidebar %>
|
<%- config.sponsor.sidebar %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
@ -299,4 +321,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
3380
package-lock.json
generated
3380
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -1,53 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "crafatar",
|
"name": "crafatar",
|
||||||
"version": "2.1.0",
|
"version": "2.1.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A blazing fast API for Minecraft faces!",
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "jomo",
|
|
||||||
"url": "https://github.com/jomo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jake",
|
|
||||||
"url": "https://github.com/Jake0oo0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/crafatar/crafatar"
|
|
||||||
},
|
|
||||||
"issues": {
|
|
||||||
"url": "https://github.com/crafatar/crafatar/issues"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"minecraft",
|
|
||||||
"avatar"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "cp 'config.example.js' 'config.js'",
|
|
||||||
"start": "node www.js",
|
"start": "node www.js",
|
||||||
"test": "mocha",
|
"test": "mocha"
|
||||||
"test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.19.0"
|
"node": "12.16.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@randy.tarampi/lwip": "^1.3.1",
|
||||||
"canvas": "^2.6.1",
|
"canvas": "^2.6.1",
|
||||||
"crc": "^3.8.0",
|
"crc": "^3.8.0",
|
||||||
"ejs": "^3.0.1",
|
"ejs": "^3.1.5",
|
||||||
"@randy.tarampi/lwip": "^1.1.0",
|
"mime": "^2.4.6",
|
||||||
"mime": "^2.4.4",
|
|
||||||
"node-df": "crafatar/node-df",
|
|
||||||
"redis": "^3.0.2",
|
"redis": "^3.0.2",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"toobusy-js": "^0.5.1"
|
"toobusy-js": "^0.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"coveralls": "^3.0.11",
|
"mocha": "^7.2.0"
|
||||||
"istanbul": "^0.4.5",
|
|
||||||
"mocha": "^7.1.1",
|
|
||||||
"mocha-lcov-reporter": "^1.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
test/bulk.sh
63
test/bulk.sh
@ -1,36 +1,55 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
hostname="crafatar.com"
|
||||||
async="true"
|
async="true"
|
||||||
|
random="false"
|
||||||
interval="0.1"
|
interval="0.1"
|
||||||
if [ "$1" = "-s" ]; then
|
|
||||||
async=""
|
|
||||||
shift
|
|
||||||
elif [ "$1" = "-i" ]; then
|
|
||||||
interval="$2"
|
|
||||||
shift 2
|
|
||||||
fi
|
|
||||||
host="$1"
|
|
||||||
shift
|
|
||||||
if [ -z "$host" ] || [ ! -z "$@" ]; then
|
|
||||||
echo "Usage: $0 [-s | -i <interval>] <host uri>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# insert newline after uuids
|
usage() {
|
||||||
ids="$(cat 'uuids.txt')"
|
echo "Usage: $0 [-s | -r | -i <interval> | -h <hostname>]... <host uri>" >&2
|
||||||
# `brew install coreutils` on OS X
|
exit 1
|
||||||
ids="$(shuf <<< "$ids" 2>/dev/null || gshuf <<< "$ids")"
|
}
|
||||||
|
|
||||||
|
get_ids() {
|
||||||
|
local shuf
|
||||||
|
if [ "$random" = "true" ]; then
|
||||||
|
while true; do uuid -v 4; done
|
||||||
|
else
|
||||||
|
# `brew install coreutils` on OS X for gshuf
|
||||||
|
shuf=$(command -v shuf gshuf)
|
||||||
|
# randomize ids
|
||||||
|
$shuf < uuids.txt
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
bulk() {
|
bulk() {
|
||||||
trap return INT
|
trap return INT # return from this function on Ctrl+C
|
||||||
echo "$ids" | while read id; do
|
get_ids | while read id; do
|
||||||
if [ -z "$async" ]; then
|
if [ "$async" = "false" ]; then
|
||||||
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
|
curl -H "Host: $hostname" -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
|
||||||
else
|
else
|
||||||
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
|
curl -H "Host: $hostname" -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
|
||||||
sleep "$interval"
|
sleep "$interval"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while [ $# != 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-s)
|
||||||
|
async="false";;
|
||||||
|
-r)
|
||||||
|
random="true";;
|
||||||
|
-i)
|
||||||
|
interval="$2"
|
||||||
|
shift;;
|
||||||
|
*)
|
||||||
|
[ -n "$host" ] && usage
|
||||||
|
host="$1";;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -z "$host" ] && usage
|
||||||
|
|
||||||
time bulk
|
time bulk
|
||||||
122
test/test.js
122
test/test.js
@ -3,13 +3,12 @@
|
|||||||
|
|
||||||
// no spam
|
// no spam
|
||||||
var logging = require("../lib/logging");
|
var logging = require("../lib/logging");
|
||||||
if (process.env.VERBOSE_TEST !== "true" && process.env.TRAVIS !== "true") {
|
if (process.env.VERBOSE_TEST !== "true") {
|
||||||
logging.log = logging.debug = logging.warn = logging.error = function() {};
|
logging.log = logging.debug = logging.warn = logging.error = function() {};
|
||||||
}
|
}
|
||||||
|
|
||||||
var networking = require("../lib/networking");
|
var networking = require("../lib/networking");
|
||||||
var helpers = require("../lib/helpers");
|
var helpers = require("../lib/helpers");
|
||||||
var cleaner = require("../lib/cleaner");
|
|
||||||
var request = require("request");
|
var request = require("request");
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
var server = require("../lib/server");
|
var server = require("../lib/server");
|
||||||
@ -68,10 +67,6 @@ describe("Crafatar", function() {
|
|||||||
console.log("Flushing and waiting for redis ...");
|
console.log("Flushing and waiting for redis ...");
|
||||||
cache.get_redis().flushall(function() {
|
cache.get_redis().flushall(function() {
|
||||||
console.log("Redis flushed!");
|
console.log("Redis flushed!");
|
||||||
// cause I don't know how big hard drives are these days
|
|
||||||
config.cleaner.disk_limit = Infinity;
|
|
||||||
config.cleaner.redis_limit = Infinity;
|
|
||||||
cleaner.run();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -93,8 +88,8 @@ describe("Crafatar", function() {
|
|||||||
assert.strictEqual(helpers.id_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
|
assert.strictEqual(helpers.id_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it("dashed uuid is valid", function(done) {
|
it("dashed uuid is not valid", function(done) {
|
||||||
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), true);
|
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), false);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it("username is invalid", function(done) {
|
it("username is invalid", function(done) {
|
||||||
@ -163,7 +158,7 @@ describe("Crafatar", function() {
|
|||||||
it("should time out on skin download", function(done) {
|
it("should time out on skin download", function(done) {
|
||||||
var original_timeout = config.http_timeout;
|
var original_timeout = config.http_timeout;
|
||||||
config.server.http_timeout = 1;
|
config.server.http_timeout = 1;
|
||||||
networking.get_from(rid(), "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
|
networking.get_from(rid(), config.endpoints.textures_url + "477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
|
||||||
assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(error.code), -1);
|
assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(error.code), -1);
|
||||||
config.server.http_timeout = original_timeout;
|
config.server.http_timeout = original_timeout;
|
||||||
done();
|
done();
|
||||||
@ -171,7 +166,7 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
it("should not find the skin", function(done) {
|
it("should not find the skin", function(done) {
|
||||||
assert.doesNotThrow(function() {
|
assert.doesNotThrow(function() {
|
||||||
networking.get_from(rid(), "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
|
networking.get_from(rid(), config.endpoints.textures_url + "this-does-not-exist", function(img, response, err) {
|
||||||
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -263,7 +258,7 @@ describe("Crafatar", function() {
|
|||||||
var url = "http://localhost:3000/%61%76%61%74%61%72%73/%61%65%37%39%35%61%61%38%36%33%32%37%34%30%38%65%39%32%61%62%32%35%63%38%61%35%39%66%33%62%61%31"; // avatars/ae795aa86327408e92ab25c8a59f3ba1
|
var url = "http://localhost:3000/%61%76%61%74%61%72%73/%61%65%37%39%35%61%61%38%36%33%32%37%34%30%38%65%39%32%61%62%32%35%63%38%61%35%39%66%33%62%61%31"; // avatars/ae795aa86327408e92ab25c8a59f3ba1
|
||||||
request.get(url, function(error, res, body) {
|
request.get(url, function(error, res, body) {
|
||||||
assert.ifError(error);
|
assert.ifError(error);
|
||||||
assert.strictEqual(res.statusCode, 201);
|
assert.strictEqual(res.statusCode, 200);
|
||||||
assert_headers(res);
|
assert_headers(res);
|
||||||
assert(res.headers.etag);
|
assert(res.headers.etag);
|
||||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||||
@ -286,7 +281,7 @@ describe("Crafatar", function() {
|
|||||||
function req() {
|
function req() {
|
||||||
request.get(url, function(error, res, body) {
|
request.get(url, function(error, res, body) {
|
||||||
assert.ifError(error);
|
assert.ifError(error);
|
||||||
assert.strictEqual(res.statusCode === 201 || res.statusCode === 200, true);
|
assert.strictEqual(res.statusCode, 200);
|
||||||
assert_headers(res);
|
assert_headers(res);
|
||||||
assert(res.headers.etag);
|
assert(res.headers.etag);
|
||||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||||
@ -303,20 +298,24 @@ describe("Crafatar", function() {
|
|||||||
var server_tests = {
|
var server_tests = {
|
||||||
"avatar with existing uuid": {
|
"avatar with existing uuid": {
|
||||||
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||||
crc32: [3337292777],
|
crc32: [4264176600],
|
||||||
|
},
|
||||||
|
"avatar with existing dashed uuid": {
|
||||||
|
url: "http://localhost:3000/avatars/853c80ef-3c37-49fd-aa49938b674adae6?size=16",
|
||||||
|
crc32: [4264176600],
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid": {
|
"avatar with non-existent uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
|
||||||
crc32: [2416827277, 1243826040],
|
crc32: [3348154329],
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid defaulting to mhf_alex": {
|
"avatar with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
|
||||||
crc32: [862751081, 809395677],
|
crc32: [73899130],
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid defaulting to uuid": {
|
"avatar with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [0],
|
crc32: [0],
|
||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
redirect: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid defaulting to url": {
|
"avatar with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -325,20 +324,20 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"overlay avatar with existing uuid": {
|
"overlay avatar with existing uuid": {
|
||||||
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
|
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
|
||||||
crc32: [1710265722],
|
crc32: [575355728],
|
||||||
},
|
},
|
||||||
"overlay avatar with non-existent uuid": {
|
"overlay avatar with non-existent uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
|
||||||
crc32: [2416827277, 1243826040],
|
crc32: [3348154329],
|
||||||
},
|
},
|
||||||
"overlay avatar with non-existent uuid defaulting to mhf_alex": {
|
"overlay avatar with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
|
||||||
crc32: [862751081, 809395677],
|
crc32: [73899130],
|
||||||
},
|
},
|
||||||
"overlay avatar with non-existent uuid defaulting to uuid": {
|
"overlay avatar with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [0],
|
crc32: [0],
|
||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
redirect: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||||
},
|
},
|
||||||
"overlay avatar with non-existent uuid defaulting to url": {
|
"overlay avatar with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -347,7 +346,7 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"cape with existing uuid": {
|
"cape with existing uuid": {
|
||||||
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [2556702429],
|
crc32: [985789174, 2099310578],
|
||||||
},
|
},
|
||||||
"cape with non-existent uuid": {
|
"cape with non-existent uuid": {
|
||||||
url: "http://localhost:3000/capes/00000000000000000000000000000000",
|
url: "http://localhost:3000/capes/00000000000000000000000000000000",
|
||||||
@ -360,20 +359,20 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"skin with existing uuid": {
|
"skin with existing uuid": {
|
||||||
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [26500336],
|
crc32: [1759176487],
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid": {
|
"skin with non-existent uuid": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000",
|
||||||
crc32: [981937087],
|
crc32: [1853029228],
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid defaulting to mhf_alex": {
|
"skin with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
|
||||||
crc32: [2298915739],
|
crc32: [427506205],
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid defaulting to uuid": {
|
"skin with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [0],
|
crc32: [0],
|
||||||
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
|
redirect: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid defaulting to url": {
|
"skin with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -395,7 +394,7 @@ describe("Crafatar", function() {
|
|||||||
"head render with non-existent uuid defaulting to uuid": {
|
"head render with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [0],
|
crc32: [0],
|
||||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
redirect: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||||
},
|
},
|
||||||
"head render with non-existent uuid defaulting to url": {
|
"head render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -417,7 +416,7 @@ describe("Crafatar", function() {
|
|||||||
"overlay head with non-existent uuid defaulting to uuid": {
|
"overlay head with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [0],
|
crc32: [0],
|
||||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
|
redirect: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
|
||||||
},
|
},
|
||||||
"overlay head render with non-existent uuid defaulting to url": {
|
"overlay head render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -426,7 +425,7 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"body render with existing uuid": {
|
"body render with existing uuid": {
|
||||||
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||||
crc32: [2745192436],
|
crc32: [1144887125],
|
||||||
},
|
},
|
||||||
"body render with non-existent uuid": {
|
"body render with non-existent uuid": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
|
||||||
@ -434,12 +433,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"body render with non-existent uuid defaulting to mhf_alex": {
|
"body render with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
|
||||||
crc32: [1255106465],
|
crc32: [4280894468],
|
||||||
},
|
},
|
||||||
"body render with non-existent uuid defaulting to uuid": {
|
"body render with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: [0],
|
crc32: [0],
|
||||||
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
redirect: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||||
},
|
},
|
||||||
"body render with non-existent uuid defaulting to url": {
|
"body render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -448,7 +447,7 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"overlay body render with existing uuid": {
|
"overlay body render with existing uuid": {
|
||||||
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
|
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
|
||||||
crc32: [2441671793],
|
crc32: [1107696668],
|
||||||
},
|
},
|
||||||
"overlay body render with non-existent uuid": {
|
"overlay body render with non-existent uuid": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
|
||||||
@ -456,7 +455,7 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"overlay body render with non-existent uuid defaulting to mhf_alex": {
|
"overlay body render with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
|
||||||
crc32: [1255106465],
|
crc32: [4280894468],
|
||||||
},
|
},
|
||||||
"overlay body render with non-existent uuid defaulting to url": {
|
"overlay body render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
@ -494,7 +493,7 @@ describe("Crafatar", function() {
|
|||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||||
assert.strictEqual(res.statusCode, res.headers["x-storage-type"] === "downloaded" ? 201 : 200);
|
assert.strictEqual(res.statusCode, 200);
|
||||||
assert(res.headers.etag);
|
assert(res.headers.etag);
|
||||||
assert.strictEqual(res.headers.etag, '"' + hash + '"');
|
assert.strictEqual(res.headers.etag, '"' + hash + '"');
|
||||||
assert_cache(location.url, res.headers.etag, function() {
|
assert_cache(location.url, res.headers.etag, function() {
|
||||||
@ -540,7 +539,7 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return a 422 (invalid render type)", function(done) {
|
it("should return a 422 (invalid render type)", function(done) {
|
||||||
request.get("http://localhost:3000/renders/invalid/Jake_0", function(error, res, body) {
|
request.get("http://localhost:3000/renders/invalid/2d5aa9cdaeb049189930461fc9b91cc5", function(error, res, body) {
|
||||||
assert.ifError(error);
|
assert.ifError(error);
|
||||||
assert.strictEqual(res.statusCode, 422);
|
assert.strictEqual(res.statusCode, 422);
|
||||||
done();
|
done();
|
||||||
@ -569,6 +568,30 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
}(loc));
|
}(loc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("should return /public resources", function(done) {
|
||||||
|
request.get("http://localhost:3000/javascript/crafatar.js", function(error, res, body) {
|
||||||
|
assert.ifError(error);
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not allow path traversal on /public", function(done) {
|
||||||
|
request.get("http://localhost:3000/../server.js", function(error, res, body) {
|
||||||
|
assert.ifError(error);
|
||||||
|
assert.strictEqual(res.statusCode, 404);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not allow encoded path traversal on /public", function(done) {
|
||||||
|
request.get("http://localhost:3000/%2E%2E/server.js", function(error, res, body) {
|
||||||
|
assert.ifError(error);
|
||||||
|
assert.strictEqual(res.statusCode, 404);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// we have to make sure that we test both a 32x64 and 64x64 skin
|
// we have to make sure that we test both a 32x64 and 64x64 skin
|
||||||
@ -589,7 +612,7 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Cape", function() {
|
describe("Networking: Cape", function() {
|
||||||
it("should not fail (guaranteed cape)", function(done) {
|
it("should not fail (guaranteed cape)", function(done) {
|
||||||
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "61699b2ed3274a019f1e0ea8c3f06bc6", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -598,13 +621,13 @@ describe("Crafatar", function() {
|
|||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "61699b2ed3274a019f1e0ea8c3f06bc6", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should not be found", function(done) {
|
it("should not be found", function(done) {
|
||||||
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.strictEqual(img, null);
|
assert.strictEqual(img, null);
|
||||||
done();
|
done();
|
||||||
@ -614,7 +637,7 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Skin", function() {
|
describe("Networking: Skin", function() {
|
||||||
it("should not fail", function(done) {
|
it("should not fail", function(done) {
|
||||||
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -623,7 +646,7 @@ describe("Crafatar", function() {
|
|||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -687,15 +710,28 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
|
|
||||||
describe("Errors", function() {
|
describe("Errors", function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uuid SHOULD be rate limited", function(done) {
|
// Mojang has changed its rate limiting, so we no longer expect to hit the rate limit
|
||||||
|
// it("uuid SHOULD be rate limited", function(done) {
|
||||||
|
// networking.get_profile(rid(), uuid, function() {
|
||||||
|
// networking.get_profile(rid(), uuid, function(err, profile) {
|
||||||
|
// assert.strictEqual(err.toString(), "HTTP: 429");
|
||||||
|
// assert.strictEqual(profile, null);
|
||||||
|
// done();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
it("CloudFront rate limit is handled", function(done) {
|
||||||
|
var original_rate_limit = config.server.sessions_rate_limit;
|
||||||
|
config.server.sessions_rate_limit = 1;
|
||||||
networking.get_profile(rid(), uuid, function() {
|
networking.get_profile(rid(), uuid, function() {
|
||||||
networking.get_profile(rid(), uuid, function(err, profile) {
|
networking.get_profile(rid(), uuid, function(err, profile) {
|
||||||
assert.strictEqual(err.toString(), "HTTP: 429");
|
assert.strictEqual(err.code, "RATELIMIT");
|
||||||
assert.strictEqual(profile, null);
|
config.server.sessions_rate_limit = original_rate_limit;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4424,4 +4424,4 @@ ffdd082bf54e415b943a8713f2885913
|
|||||||
ffe0be5f0cab4b3785f67974c23660bb
|
ffe0be5f0cab4b3785f67974c23660bb
|
||||||
ffe3d4c861354928b932794d85a30567
|
ffe3d4c861354928b932794d85a30567
|
||||||
ffe72a222ac9463d81d3ee5eafb7f68e
|
ffe72a222ac9463d81d3ee5eafb7f68e
|
||||||
fff854a189644f12b92764fdb4573f8b
|
fff854a189644f12b92764fdb4573f8b
|
||||||
|
|||||||
4
www.js
4
www.js
@ -1,5 +1,5 @@
|
|||||||
|
var networking = require("./lib/networking");
|
||||||
var logging = require("./lib/logging");
|
var logging = require("./lib/logging");
|
||||||
var cleaner = require("./lib/cleaner");
|
|
||||||
var config = require("./config");
|
var config = require("./config");
|
||||||
|
|
||||||
process.on("uncaughtException", function(err) {
|
process.on("uncaughtException", function(err) {
|
||||||
@ -7,6 +7,6 @@ process.on("uncaughtException", function(err) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
setInterval(cleaner.run, config.cleaner.interval * 1000);
|
setInterval(networking.resetCounter, 1000);
|
||||||
|
|
||||||
require("./lib/server.js").boot();
|
require("./lib/server.js").boot();
|
||||||
Loading…
x
Reference in New Issue
Block a user