Skip to content

Commit f58962f

Browse files
author
Antonio Muñoz
committed
Merge branch 'develop'
2 parents 2a8fdf5 + 68615a7 commit f58962f

102 files changed

Lines changed: 2743 additions & 2757 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
sudo: required
2+
language: java
3+
jdk:
4+
- oraclejdk8
5+
branches:
6+
only:
7+
- develop
8+
install: ./gradlew clean build
9+
script: ./gradlew test jacocoTestReport
10+
before_install:
11+
- curl -sL https://github.com/jpm4j/jpm4j.installers/raw/master/dist/biz.aQute.jpm.run.jar >jpm4j.jar
12+
- java -jar jpm4j.jar -u init
13+
- ~/jpm/bin/jpm install com.codacy:codacy-coverage-reporter:assembly
14+
after_success:
15+
- ~/jpm/bin/codacy-coverage-reporter -l Java -r build/reports/jacoco/test/jacocoTestReport.xml
16+

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 Antonio Muñoz
3+
Copyright (c) 2015-2017 Antonio Muñoz
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ It's nice because:
2323

2424
RESP can serialize some data types
2525

26-
- simple strings: `+PONG`
27-
- errors: `-ERROR`
28-
- integers: `:1`
29-
- bulk strings (binary-safe): `$4\r\nPING`
30-
- arrays: `*3\r\n:1\r\n:2\r\n:3`
26+
- simple strings: `+PONG\r\n`
27+
- errors: `-ERROR\r\n`
28+
- integers: `:1\r\n`
29+
- bulk strings (binary-safe): `$4\r\nPING\r\n`
30+
- arrays: `*3\r\n:1\r\n:2\r\n:3\r\n`
3131

3232
What binary safe means? It means that can be what ever you want, a UTF-8 String
3333
or compressed data, a picture, etc...
@@ -131,11 +131,24 @@ the number of the parameter accepted for this command
131131
If the number of parameters is less than the especified value, the command
132132
is rejected with an error.
133133

134+
## Maven
135+
136+
<dependency>
137+
<groupId>com.github.tonivade</groupId>
138+
<artifactId>resp-server</artifactId>
139+
<version>0.5.0</version>
140+
</dependency>
141+
142+
## Gradle
143+
144+
compile 'com.github.tonivade:resp-server:0.5.0'
145+
134146
## Continuous Integration
135147

136-
[![Build Status](https://drone.io/github.com/tonivade/resp-server/status.png)](https://drone.io/github.com/tonivade/resp-server/latest)
148+
[![Build Status](https://api.travis-ci.org/tonivade/resp-server.svg?branch=develop)](https://travis-ci.org/tonivade/resp-server)
137149

138-
[![Coverage Status](https://coveralls.io/repos/github/tonivade/resp-server/badge.svg?branch=develop)](https://coveralls.io/github/tonivade/resp-server?branch=develop)
150+
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/47b2b3213b7248eca911e4783ed6d031)](https://www.codacy.com/app/tonivade/resp-server?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=tonivade/resp-server&amp;utm_campaign=Badge_Grade)
151+
[![Codacy Coverage](https://api.codacy.com/project/badge/Coverage/47b2b3213b7248eca911e4783ed6d031)](https://www.codacy.com/app/tonivade/resp-server?utm_source=github.com&utm_medium=referral&utm_content=tonivade/resp-server&utm_campaign=Badge_Coverage)
139152

140153
## LICENSE
141154

build.gradle

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ apply plugin: 'com.bmuschko.nexus'
66

77
group = 'com.github.tonivade'
88
archivesBaseName = 'resp-server'
9-
version = '0.4.0'
9+
version = '0.5.0'
1010

1111
buildscript {
1212
repositories {
@@ -55,31 +55,32 @@ repositories {
5555
}
5656

5757
dependencies {
58-
compile 'io.netty:netty-all:4.0.37.Final'
58+
compile 'io.netty:netty-all:4.0.44.Final'
5959
compile 'com.github.tonivade:equalizer:0.2.0'
60-
compile 'io.reactivex:rxjava:1.1.5'
60+
compile 'io.reactivex:rxjava:1.2.7'
61+
compile 'io.javaslang:javaslang:2.0.5'
6162
testCompile 'junit:junit:4.12'
6263
testCompile 'org.hamcrest:hamcrest-library:1.3'
63-
testCompile 'org.mockito:mockito-core:1.10.19'
64+
testCompile 'org.mockito:mockito-core:2.2.29'
6465
}
6566

6667
modifyPom {
6768
project {
68-
name 'Tiny Server'
69-
description 'Command based TCP server framework'
70-
url 'https://github.com/tonivade/tiny-server'
69+
name 'RESP Server'
70+
description 'Netty implementation of REdis Serialization Protocol, and a simple framework to implement command based protocols'
71+
url 'https://github.com/tonivade/resp-server'
7172
inceptionYear '2015'
7273

7374
scm {
74-
url 'https://github.com/tonivade/tiny-server'
75-
connection 'scm:https://github.com/tonivade/tiny-server.git'
76-
developerConnection 'scm:git://github.com/tonivade/tiny-server.git'
75+
url 'https://github.com/tonivade/resp-server'
76+
connection 'scm:https://github.com/tonivade/resp-server.git'
77+
developerConnection 'scm:git://github.com/tonivade/resp-server.git'
7778
}
7879

7980
licenses {
8081
license {
8182
name 'The MIT License (MIT)'
82-
url 'https://github.com/tonivade/tiny-server/blob/master/LICENSE'
83+
url 'https://github.com/tonivade/resp-server/blob/master/LICENSE'
8384
distribution 'repo'
8485
}
8586
}
@@ -105,3 +106,9 @@ nexus {
105106
repositoryUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
106107
snapshotRepositoryUrl = 'https://oss.sonatype.org/content/repositories/snapshots'
107108
}
109+
110+
task copyToLib(type: Copy) {
111+
into "$buildDir/output/lib"
112+
from configurations.runtime
113+
}
114+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) 2015-2017, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
3+
* Distributed under the terms of the MIT License
4+
*/
5+
package com.github.tonivade.resp;
6+
7+
import com.github.tonivade.resp.protocol.RedisToken;
8+
9+
import io.netty.channel.ChannelHandlerContext;
10+
import io.netty.channel.socket.SocketChannel;
11+
12+
public interface IRedis {
13+
void channel(SocketChannel channel);
14+
void connected(ChannelHandlerContext ctx);
15+
void disconnected(ChannelHandlerContext ctx);
16+
void receive(ChannelHandlerContext ctx, RedisToken message);
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (c) 2015-2017, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
3+
* Distributed under the terms of the MIT License
4+
*/
5+
package com.github.tonivade.resp;
6+
7+
import com.github.tonivade.resp.protocol.RedisToken;
8+
9+
public interface IRedisCallback {
10+
void onConnect();
11+
void onDisconnect();
12+
void onMessage(RedisToken token);
13+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) 2015-2017, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
3+
* Distributed under the terms of the MIT License
4+
*/
5+
package com.github.tonivade.resp;
6+
7+
import static java.util.Objects.requireNonNull;
8+
9+
import java.util.logging.Logger;
10+
11+
import com.github.tonivade.resp.protocol.RedisDecoder;
12+
import com.github.tonivade.resp.protocol.RedisEncoder;
13+
import com.github.tonivade.resp.protocol.RedisToken;
14+
15+
import io.netty.bootstrap.Bootstrap;
16+
import io.netty.buffer.PooledByteBufAllocator;
17+
import io.netty.channel.ChannelFuture;
18+
import io.netty.channel.ChannelHandlerContext;
19+
import io.netty.channel.ChannelOption;
20+
import io.netty.channel.EventLoopGroup;
21+
import io.netty.channel.nio.NioEventLoopGroup;
22+
import io.netty.channel.socket.SocketChannel;
23+
import io.netty.channel.socket.nio.NioSocketChannel;
24+
import io.netty.handler.codec.string.StringEncoder;
25+
import io.netty.util.CharsetUtil;
26+
27+
public class RedisClient implements IRedis {
28+
29+
private static final Logger LOGGER = Logger.getLogger(RedisClient.class.getName());
30+
31+
private static final String DELIMITER = "\r\n";
32+
33+
private static final int BUFFER_SIZE = 1024 * 1024;
34+
private static final int MAX_FRAME_SIZE = BUFFER_SIZE * 100;
35+
36+
private final int port;
37+
private final String host;
38+
39+
private EventLoopGroup workerGroup;
40+
private Bootstrap bootstrap;
41+
42+
private ChannelFuture future;
43+
44+
private ChannelHandlerContext context;
45+
private RedisInitializerHandler initHandler;
46+
private RedisConnectionHandler connectionHandler;
47+
48+
private final IRedisCallback callback;
49+
50+
public RedisClient(String host, int port, IRedisCallback callback) {
51+
this.host = requireNonNull(host);
52+
this.port = requireRange(port, 1024, 65535);
53+
this.callback = requireNonNull(callback);
54+
}
55+
56+
public void start() {
57+
workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());
58+
initHandler = new RedisInitializerHandler(this);
59+
connectionHandler = new RedisConnectionHandler(this);
60+
61+
bootstrap = new Bootstrap().group(workerGroup)
62+
.channel(NioSocketChannel.class)
63+
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
64+
.option(ChannelOption.SO_RCVBUF, BUFFER_SIZE)
65+
.option(ChannelOption.SO_SNDBUF, BUFFER_SIZE)
66+
.option(ChannelOption.SO_KEEPALIVE, true)
67+
.handler(initHandler);
68+
69+
future = connect();
70+
}
71+
72+
public void stop() {
73+
try {
74+
if (future != null) {
75+
future.channel().close();
76+
}
77+
} finally {
78+
workerGroup.shutdownGracefully();
79+
}
80+
}
81+
82+
@Override
83+
public void channel(SocketChannel channel) {
84+
LOGGER.info(() -> "connected to server: " + host + ":" + port);
85+
channel.pipeline().addLast("redisEncoder", new RedisEncoder());
86+
channel.pipeline().addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
87+
channel.pipeline().addLast("linDelimiter", new RedisDecoder(MAX_FRAME_SIZE));
88+
channel.pipeline().addLast(connectionHandler);
89+
}
90+
91+
@Override
92+
public void connected(ChannelHandlerContext ctx) {
93+
LOGGER.info(() -> "channel active");
94+
this.context = ctx;
95+
callback.onConnect();
96+
}
97+
98+
@Override
99+
public void disconnected(ChannelHandlerContext ctx) {
100+
LOGGER.info(() -> "client disconected from server: " + host + ":" + port);
101+
if (this.context != null) {
102+
callback.onDisconnect();
103+
this.context = null;
104+
}
105+
}
106+
107+
public void send(String message) {
108+
writeAndFlush(message + DELIMITER);
109+
}
110+
111+
public void send(RedisToken message) {
112+
writeAndFlush(message);
113+
}
114+
115+
@Override
116+
public void receive(ChannelHandlerContext ctx, RedisToken message) {
117+
callback.onMessage(message);
118+
}
119+
120+
private ChannelFuture connect() {
121+
LOGGER.info(() -> "trying to connect");
122+
ChannelFuture future = bootstrap.connect(host, port);
123+
future.syncUninterruptibly();
124+
return future;
125+
}
126+
127+
private void writeAndFlush(Object message) {
128+
if (context != null) {
129+
context.writeAndFlush(message);
130+
}
131+
}
132+
133+
private int requireRange(int value, int min, int max) {
134+
if (value <= min || value > max) {
135+
throw new IllegalArgumentException(min + " <= " + value + " < " + max);
136+
}
137+
return value;
138+
}
139+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2015-2017, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
3+
* Distributed under the terms of the MIT License
4+
*/
5+
package com.github.tonivade.resp;
6+
7+
import java.util.logging.Level;
8+
import java.util.logging.Logger;
9+
10+
import com.github.tonivade.resp.protocol.RedisToken;
11+
12+
import io.netty.channel.ChannelHandler.Sharable;
13+
import io.netty.channel.ChannelHandlerContext;
14+
import io.netty.channel.ChannelInboundHandlerAdapter;
15+
import io.netty.util.ReferenceCountUtil;
16+
17+
@Sharable
18+
public class RedisConnectionHandler extends ChannelInboundHandlerAdapter {
19+
20+
private static final Logger LOGGER = Logger.getLogger(RedisConnectionHandler.class.getName());
21+
22+
private final IRedis impl;
23+
24+
public RedisConnectionHandler(IRedis impl) {
25+
this.impl = impl;
26+
}
27+
28+
@Override
29+
public void channelActive(ChannelHandlerContext ctx) throws Exception {
30+
impl.connected(ctx);
31+
}
32+
33+
@Override
34+
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
35+
try {
36+
impl.receive(ctx, (RedisToken) msg);
37+
} finally {
38+
ReferenceCountUtil.release(msg);
39+
}
40+
}
41+
42+
@Override
43+
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
44+
LOGGER.log(Level.FINE, "channel inactive");
45+
impl.disconnected(ctx);
46+
ctx.close();
47+
}
48+
49+
@Override
50+
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
51+
LOGGER.log(Level.FINE, "uncaught exception", cause);
52+
impl.disconnected(ctx);
53+
ctx.close();
54+
}
55+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2015-2017, Antonio Gabriel Muñoz Conejo <antoniogmc at gmail dot com>
3+
* Distributed under the terms of the MIT License
4+
*/
5+
package com.github.tonivade.resp;
6+
7+
import io.netty.channel.ChannelHandlerContext;
8+
import io.netty.channel.ChannelInitializer;
9+
import io.netty.channel.socket.SocketChannel;
10+
11+
public class RedisInitializerHandler extends ChannelInitializer<SocketChannel> {
12+
13+
private final IRedis impl;
14+
15+
public RedisInitializerHandler(IRedis impl) {
16+
this.impl = impl;
17+
}
18+
19+
@Override
20+
protected void initChannel(SocketChannel channel) throws Exception {
21+
impl.channel(channel);
22+
}
23+
24+
@Override
25+
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
26+
impl.disconnected(ctx);
27+
}
28+
}

0 commit comments

Comments
 (0)