-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a075a41
Showing
9 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
container: | ||
image: crystallang/crystal | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Install dependencies | ||
run: shards install | ||
- name: Run Tests | ||
run: crystal spec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Responder | ||
|
||
![CI](https://github.com/Green-Edge/responder/workflows/CI/badge.svg) | ||
|
||
A simple RESP server which accepts requests, pattern matches the | ||
command using regexp, and returns a corresponding response from a YAML | ||
file. | ||
|
||
## Usage | ||
|
||
The YAML file format is as follows: | ||
|
||
```yaml | ||
host: "{your IP or localhost}" | ||
port: {your port, for example 3001} | ||
rules: | ||
- match: "{a regex}" | ||
wait: {int} | ||
response: "{response value}" | ||
``` | ||
Rules are made up of the following values: | ||
- `match`: a regular expression which should match the RESP | ||
command and arguments, in the format you would issue via | ||
`redis-cli` | ||
- `wait`: an optional delay, in milliseconds, before returning | ||
the result | ||
- `response`: the response value | ||
|
||
### Regular expressions | ||
|
||
Captures can be used in the regular expression, and those | ||
captures can be returned in the response. For example: | ||
|
||
```yaml | ||
rules: | ||
- match: "HELLO ([^\\s]+)" # regex | ||
response: >- | ||
{"success": true, "result": "HELLO $1"} | ||
``` | ||
|
||
Calling this with `HELLO world` will capture `world` and | ||
return it in the place of `$1` in the response. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
host: 0.0.0.0 | ||
port: 3001 | ||
rules: | ||
- match: "^PING" | ||
wait: 1000 | ||
response: "PONG" | ||
- match: "HELLO ([^\\s]+)" # regex | ||
wait: 1000 # ms | ||
response: >- | ||
{"success": true, "result": "HELLO $1"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/Volumes/Data/Clients/greenedge/resp.cr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
name: responder | ||
version: 0.1.0 | ||
|
||
authors: | ||
- Phillip Oldham <phillip@greenedgecloud.com> | ||
|
||
description: | | ||
A simple RESP server which accepts requests, | ||
pattern matches the command using regexp, | ||
and returns a corresponding response from | ||
a YAML file. | ||
dependencies: | ||
resp-server: | ||
github: Green-Edge/resp.cr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
require "json" | ||
require "log" | ||
require "spec" | ||
require "../src/server" | ||
|
||
Log.setup(:debug) | ||
|
||
configfile = "example.yml" | ||
|
||
describe "responder" do | ||
context "standard commands" do | ||
it "should respond to a simple 'PING' command" do | ||
responder = Responder.new(configfile) | ||
|
||
responder.process("PING", [] of String).should eq("PONG") | ||
end | ||
|
||
it "should process to a standard 'HELLO' command" do | ||
responder = Responder.new(configfile) | ||
|
||
result = responder.process("HELLO", ["world"]) | ||
JSON.parse(result)["success"].should eq(true) | ||
JSON.parse(result)["result"].should eq("HELLO world") | ||
end | ||
end | ||
|
||
context "bad commands" do | ||
it "should respond with an error" do | ||
responder = Responder.new(configfile) | ||
|
||
result = responder.process("unknown", [] of String) | ||
JSON.parse(result)["success"].should eq(false) | ||
JSON.parse(result)["error"].should eq("unknown operation 'unknown'") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
require "option_parser" | ||
require "./server" | ||
|
||
configfile = "" | ||
|
||
parser = OptionParser.parse do |parser| | ||
parser.banner = "Usage: responder [arguments]" | ||
parser.on("-c FILE", "--config=FILE", "Configuration YAML file") { |file| configfile = file } | ||
parser.on("-h", "--help", "Show this help") do | ||
puts parser | ||
exit | ||
end | ||
parser.invalid_option do |flag| | ||
STDERR.puts "ERROR: #{flag} is not a valid option." | ||
STDERR.puts parser | ||
exit(1) | ||
end | ||
end | ||
|
||
parser.parse | ||
|
||
if configfile == "" | ||
STDERR.puts parser | ||
exit(1) | ||
end | ||
|
||
responder = Responder.new(configfile) | ||
responder.run |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
require "log" | ||
require "yaml" | ||
require "resp-server" | ||
|
||
|
||
class Responder | ||
Log = ::Log.for(self) | ||
|
||
@config : YAML::Any = YAML.parse "{}" | ||
|
||
def initialize(configfile : String) | ||
begin | ||
@config = File.open(configfile) do |file| | ||
YAML.parse(file) | ||
end | ||
rescue File::NotFoundError | ||
Log.error { "cannot read file: #{configfile}" } | ||
exit(1) | ||
rescue ex : YAML::ParseException | ||
Log.error { "#{configfile} contains invalid YAML: #{ex}" } | ||
exit(1) | ||
end | ||
|
||
Log.debug {"Config loaded from #{configfile}, rules loaded: #{@config["rules"].size}"} | ||
|
||
@port = @config["port"]? ? @config["port"].as_i : 6379 | ||
@host = @config["host"]? ? @config["host"].as_s : "127.0.0.1" | ||
end | ||
|
||
def run | ||
Log.info {"Listening on #{@host}:#{@port}..."} | ||
server = RESP::Server.new(@host, @port) | ||
server.listen do |conn| | ||
operation, args = conn.parse | ||
conn.send_string process(operation, args) | ||
end | ||
end | ||
|
||
def process(operation, args) | ||
Log.debug {"#{self.class}: processing #{operation} with args: #{args}"} | ||
operation = | ||
"#{operation}".strip | ||
|
||
opstring = | ||
case args | ||
when Array(String) | ||
"#{operation} " + args.join(" ") | ||
when String | ||
"#{operation} #{args}" | ||
else | ||
operation | ||
end | ||
|
||
matches = @config["rules"].as_a.map do |rule| | ||
/#{rule["match"]}/.match(opstring) ? rule : nil | ||
end | ||
|
||
rule = matches.reject(nil).first? | ||
Log.debug {"#{self.class}: found rule #{rule}"} | ||
|
||
if rule.nil? | ||
return <<-END | ||
{ | ||
"success": false, | ||
"error": "unknown operation '#{operation}'" | ||
} | ||
END | ||
end | ||
|
||
response = rule["response"].to_s | ||
|
||
if md = opstring.match(/#{rule["match"]}/) | ||
md.to_a.each_with_index do |val, i| | ||
response = response.gsub("$#{i}", val) | ||
end | ||
end | ||
|
||
if rule["wait"]? | ||
sleep(Time::Span.new(nanoseconds: rule["wait"].as_i * 1_000)) | ||
end | ||
|
||
response | ||
end | ||
end |