Skip to content

Commit

Permalink
Redis-based Caching (#86)
Browse files Browse the repository at this point in the history
* Replace node-cache with redis based caching. Update the cache.getOrSet function to use redis. Update dependencies. Add docker-compose.yml.

* Update readme and tests battletag.
  • Loading branch information
alfg authored Jul 15, 2020
1 parent f6d3537 commit a762862
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 118 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2016-2018 Alfred Gutierrez
Copyright (c) 2016-2020 Alfred Gutierrez

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
110 changes: 71 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,52 +29,73 @@ If you wish to use the Javascript API in your own project, see [api/README.md](a
## Demo

```
curl http://owapi.io/profile/pc/us/Calvin-1337
curl http://owapi.io/profile/pc/us/Jay3-11118
```
```json
{
"username":"Calvin",
"level":813,
"portrait":"https://d1u1mce87gyfbn.cloudfront.net/game/unlocks/0x0250000000000EF7.png",
"games":{
"quickplay":{
"won":647
},
"competitive":{
"won":59,
"lost":48,
"draw":1,
"played":108
}
},
"playtime":{
"quickplay":"129 hours",
"competitive":"23 hours"
},
"competitive":{
"tank": {
"rank":4115,
"rank_img":"https://d1u1mce87gyfbn.cloudfront.net/game/rank-icons/season-2/rank-7.png"
},
"damage": {
"rank":4257,
"rank_img":"https://d1u1mce87gyfbn.cloudfront.net/game/rank-icons/season-2/rank-7.png"
},
"support": {
"rank":4475,
"rank_img":"https://d1u1mce87gyfbn.cloudfront.net/game/rank-icons/season-2/rank-7.png"
}

},
"levelFrame":"https://d1u1mce87gyfbn.cloudfront.net/game/playerlevelrewards/0x025000000000096F_Border.png",
"star":"https://d1u1mce87gyfbn.cloudfront.net/game/playerlevelrewards/0x025000000000096F_Rank.png"
}
"username": "Jay3",
"level": 2970,
"portrait": "https://d15f34w2p8l1cc.cloudfront.net/overwatch/190aa6150e33690e39a9c91308d5da9b2e262262657af26579b95e939c44d5ad.png",
"endorsement": {
"sportsmanship": {
"value": 0.18,
"rate": 18
},
"shotcaller": {
"value": 0.44,
"rate": 44
},
"teammate": {
"value": 0.38,
"rate": 38
},
"level": null,
"frame": "https://static.playoverwatch.com/svg/icons/endorsement-frames-3c9292c49d.svg#_2",
"icon": "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjQwIiB3aWR0aD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxjaXJjbGUgcj0iMTUuOTE1NDk0MzA5MTg5NTQiIGZpbGw9IiMyYTJiMmUiIHN0cm9rZS1kYXNoYXJyYXk9IjQ0IDU2IiBzdHJva2UtZGFzaG9mZnNldD0iMjUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlPSIjZjE5NTEyIiBjeD0iNTAlIiBjeT0iNTAlIj48L2NpcmNsZT48Y2lyY2xlIHI9IjE1LjkxNTQ5NDMwOTE4OTU0IiBmaWxsPSJ0cmFuc3BhcmVudCIgc3Ryb2tlLWRhc2hhcnJheT0iMzggNjIiIHN0cm9rZS1kYXNob2Zmc2V0PSI4MSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2U9IiNjODFhZjUiIGN4PSI1MCUiIGN5PSI1MCUiPjwvY2lyY2xlPjxjaXJjbGUgcj0iMTUuOTE1NDk0MzA5MTg5NTQiIGZpbGw9InRyYW5zcGFyZW50IiBzdHJva2UtZGFzaGFycmF5PSIxOCA4MiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjQzIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZT0iIzQwY2U0NCIgY3g9IjUwJSIgY3k9IjUwJSI+PC9jaXJjbGU+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGR5PSIuM2VtIiBmb250LWZhbWlseT0iY2VudHVyeSBnb3RoaWMsYXJpYWwsc2Fucy1zZXJpZiIgZm9udC13ZWlnaHQ9IjMwMCIgZm9udC1zaXplPSIxNiIgc3Ryb2tlPSIjZjZmNmY2IiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9IiNmNmY2ZjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5hTjwvdGV4dD48L3N2Zz4="
},
"private": false,
"games": {
"quickplay": {
"won": 925,
"played": 1671
},
"competitive": {
"won": 145,
"lost": 121,
"draw": 4,
"played": 270,
"win_rate": 54.51
}
},
"playtime": {
"quickplay": "201:16:17",
"competitive": "55:14:59"
},
"competitive": {
"tank": {
"rank": null,
"rank_img": null
},
"damage": {
"rank": 4553,
"rank_img": "https://d1u1mce87gyfbn.cloudfront.net/game/rank-icons/rank-GrandmasterTier.png"
},
"support": {
"rank": null,
"rank_img": null
}
},
"levelFrame": "https://d15f34w2p8l1cc.cloudfront.net/overwatch/9e8600f97ea4a84d822d8b336f2b1dbfe7372fb9f2b6bf1d0336193567f6f943.png",
"star": "https://d15f34w2p8l1cc.cloudfront.net/overwatch/cd877430ccc400c10e24507dba972e24a4543edc05628045300f1349cf003f3a.png"
}}
```

## Install

#### Requirements
* Node v8.0+
* Redis
* Or Docker

```bash
git clone https://github.com/alfg/overwatch-api.git
Expand All @@ -83,9 +104,20 @@ npm install
npm start
```

Or deploy your own Heroku instance!
#### Environment Variables
Set the following environment variables if you would like to override the default configuration.
```
REDIS_URL=redis://localhost:6379
CACHE_TTL=3600
```

#### Docker
A `docker-compose.yml` and `Dockerfile` are provided to easily setup an environment.

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/alfg/overwatch-api)
```
docker-compose build
docker-compose up
```

#### Development
This project is built using [srv](https://github.com/alfg/srv), a microservices stack based on [express](https://expressjs.com/). After installation, run the project using the following:
Expand Down
2 changes: 1 addition & 1 deletion api/test/parser/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getProfile } from '../../src/parser';

const platform = 'pc'
const region = 'us'
const tag = 'xQc-11273'
const tag = 'Jay3-11118'

var result;

Expand Down
2 changes: 1 addition & 1 deletion api/test/parser/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getStats } from '../../src/parser';

const platform = 'pc'
const region = 'us'
const tag = 'xQc-11273'
const tag = 'Jay3-11118'

var result;

Expand Down
45 changes: 14 additions & 31 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
version: "3.3"
version: "3.8"

services:
traefik:
image: traefik:v2.2
container_name: "traefik"
command:
- "--log.level=DEBUG"
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
overwatch-api:
# image: alfg/overwatch-api:latest
build: ./
ports:
- "80:80"
- "8080:8080"
restart: always
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"

api:
image: alfg/overwatch-api:latest
container_name: "api"
ports:
- "3000"
- "3000:3000"
environment:
- "CACHE_TTL=1200"
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`owapi.docker.localhost`, `owapi.io`, `www.owapi.io`, `api.owapi.io`)"
- "traefik.http.routers.api.entrypoints=web"
- "traefik.http.services.api.loadbalancer.server.port=3000"
- "traefik.http.routers.api.middlewares=ratelimit"
- "traefik.http.middlewares.ratelimit.ratelimit.average=20"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=15"
restart: always
- CACHE_TTL=30
- REDIS_URL=redis://redis:6379
links:
- redis

redis:
image: redis
ports:
- "6379:6379"
48 changes: 37 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "overwatch-api-server",
"version": "0.9.0",
"version": "0.10.0",
"description": "An Unoffical Overwatch HTTP API",
"main": "server/index.js",
"engines": {
Expand All @@ -9,7 +9,7 @@
"dependencies": {
"async": "^2.6.1",
"cheerio": "^0.22.0",
"node-cache": "^3.2.1",
"redis": "^3.0.2",
"request": "^2.74.0",
"request-promise": "^4.1.1",
"srv-cli": "0.4.1",
Expand Down
56 changes: 24 additions & 32 deletions server/cache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var NodeCache = require('node-cache');
var myCache = new NodeCache();
import redis from 'redis';
import config from './config';

var cache = {
/** Gets key from cache if exists, else sets the cache and returns data.
Expand All @@ -9,43 +9,35 @@ var cache = {
* @param {Function} callback - Callback function to send back data or value.
*/
getOrSet: function(cacheKey, timeout, fn, callback) {
myCache.get(cacheKey, function(err, value) {
if (!err) {
if (value == undefined) {
fn(function(data) {
if (!(data.statusCode)) {
myCache.set(cacheKey, data, timeout);
}
callback(data);
});
} else {
callback(value);
}
}
// Make redis connection.
const client = redis.createClient({
url: config.REDIS_URL
});
client.on('error', (err) => {
return callback(err);
});
},

get: function(cacheKey, callback) {
myCache.get(cacheKey, function(err, value) {
// Get cacheKey. If cacheKey is not present (or expired), then set the key with a timeout.
client.get(cacheKey, (err, reply) => {
if (!err) {
if (value == undefined) {
callback(value);
if (!reply) {

// Run function to get data to cache.
fn((data) => {
if (!data.statusCode) {
client.set(cacheKey, JSON.stringify(data), 'EX', timeout, (err, reply) => {
if (err) {
return callback(err);
}
});
}
return callback(data);
});
} else {
callback(value);
return callback(JSON.parse(reply));
}
}
});
},

set: function(cacheKey, timeout, fn, callback) {
fn(function(data) {
myCache.set(cacheKey, data, timeout, function(err, success) {
if (!err && success) {
callback(data);
}
callback();
});
});
}
}

Expand Down
1 change: 1 addition & 0 deletions server/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const config = {
CACHE_TTL: process.env.CACHE_TTL || 60 * 5, // 5 minute default.
REDIS_URL: process.env.REDIS_URL || 'redis://localhost:6379',
};

export default config;

0 comments on commit a762862

Please sign in to comment.