Skip to content
Permalink
Browse files
docs: add README.md (#17)
  • Loading branch information
tzssangglass committed May 30, 2021
1 parent ce3ebda commit 732cd2de2a3b10dc92c417933cc93eda0bd0dad2
Showing 16 changed files with 247 additions and 29 deletions.
@@ -0,0 +1,50 @@
apisix-java-plugin-runner
=================

Runs [Apache APISIX](http://apisix.apache.org/) plugins written in Java.
Implemented as a sidecar that accompanies APISIX.

![apisix-java-plugin-runner-overview](./docs/images/apisix-java-plugin-runner-overview.png)

Status
------

This project is currently considered experimental.

Why apisix-java-plugin-runner
---------------------

APISIX offers many full-featured plugins covering areas such as authentication,
security, traffic control, serverless, analytics & monitoring, transformations, logging.

It also provides highly extensible API, allowing common phases to be mounted,
and users can use these api to develop their own plugins.

APISIX supports writing plugins in multiple languages in version [2.6.0](https://github.com/apache/apisix/blob/master/CHANGELOG.md#260),
this project is APISIX Java side implementation that supports writing plugins in java.


How it Works
-------------

See [How it Works](./docs/how-it-works.md) to learn how apisix-java-plugin-runner collaborate
with APISIX to run plugins written in java.

The Internal of apisix-java-plugin-runner
---------------------------------

If you're interested in the internal of apisix-java-plugin-runner, we recommend you
to read the [the-internal-of-apisix-java-plugin-runner](./docs/the-internal-of-apisix-java-plugin-runner.md),
it explains the details of communication and protocol conversion with APISIX.

Get Involved in Development
---------------------------

Welcome to make contributions, but before you start, please check out
[development.md](./docs/development.md) to learn how to run and debug apisix-java-plugin-runner
in your own environment.

License
-------

[Apache 2.0 LICENSE](./LICENSE)
Empty file.
Empty file.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
@@ -21,6 +21,8 @@
import io.netty.handler.logging.LoggingHandler;
import lombok.RequiredArgsConstructor;
import org.apache.apisix.plugin.runner.handler.IOHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@@ -34,9 +36,11 @@
@RequiredArgsConstructor
public class ApplicationRunner implements CommandLineRunner {

private final Logger logger = LoggerFactory.getLogger(ApplicationRunner.class);

private final TcpServer tcpServer;

@Value("${socket.file:/tmp/runner.sock}")
@Value("${socket.file}")
private String socketFile;

private final IOHandler handler;
@@ -50,6 +54,11 @@ public void run(String... args) throws Exception {
if (Objects.isNull(tcpServer)) {
tcpServer = TcpServer.create();
}

if (socketFile.startsWith("unix:")) {
socketFile = socketFile.substring("unix:".length());
}

tcpServer = tcpServer.bindAddress(() -> new DomainSocketAddress(socketFile));
tcpServer = tcpServer.doOnChannelInit((observer, channel, addr) -> {
// if (Objects.nonNull(channelHandlers)) {
@@ -62,6 +71,7 @@ public void run(String... args) throws Exception {
tcpServer = tcpServer.handle(handler::handle);
}
this.server = tcpServer.bindNow();
logger.warn("java runner is listening on the socket file: {}", socketFile);

// delete socket file when tcp server shutdown
Runtime.getRuntime()
@@ -32,6 +32,6 @@ public class CacheConfiguration {
@Bean
public Cache<Long, A6Conf> configurationCache(@Value("${cache.config.expired:3610}") long expired,
@Value("${cache.config.capacity:1000}") int capacity) {
return CacheBuilder.newBuilder().expireAfterWrite(expired, TimeUnit.SECONDS).maximumSize(capacity).build();
return CacheBuilder.newBuilder().expireAfterWrite(expired + 10, TimeUnit.SECONDS).maximumSize(capacity).build();
}
}
@@ -21,7 +21,6 @@
import io.github.api7.A6.HTTPReqCall.Action;
import io.github.api7.A6.HTTPReqCall.Rewrite;
import io.github.api7.A6.HTTPReqCall.Stop;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.apache.apisix.plugin.runner.A6ConfigResponse;
import org.apache.apisix.plugin.runner.A6ErrResponse;
import org.apache.apisix.plugin.runner.HttpResponse;
@@ -132,8 +131,8 @@ void testRewriteResponseEncode() {
void testStopResponseEncode() {
HttpResponse httpResponse = new HttpResponse(0L);
// set status, body, resp header means stop request
httpResponse.setStatus(HttpResponseStatus.UNAUTHORIZED);
httpResponse.setRespHeaders("code", "401");
httpResponse.setStatusCode(401);
httpResponse.setHeader("code", "401");
httpResponse.setBody("Unauthorized");
ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
result.position(4);
@@ -161,8 +160,8 @@ void testMixStopAndRewriteResponseEncode() {
httpResponse.setReqHeader("Server", "APISIX");

// set status, body, resp header means stop request
httpResponse.setStatus(HttpResponseStatus.UNAUTHORIZED);
httpResponse.setRespHeaders("code", "401");
httpResponse.setStatusCode(401);
httpResponse.setHeader("code", "401");
httpResponse.setBody("Unauthorized");
ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
result.position(4);
@@ -23,7 +23,6 @@
import io.github.api7.A6.Err.Code;
import io.github.api7.A6.HTTPReqCall.Action;
import io.github.api7.A6.TextEntry;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.apache.apisix.plugin.runner.A6Conf;
import org.apache.apisix.plugin.runner.A6ConfigRequest;
import org.apache.apisix.plugin.runner.A6ConfigResponse;
@@ -114,7 +113,7 @@ public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilte
logger.info("do filter: CatFilter, order: {}", getOrder());
logger.info("do filter: CatFilter, config: {}", request.getConfig(this));

response.setStatus(HttpResponseStatus.UNAUTHORIZED);
response.setStatusCode(401);
return chain.filter(request, response);
}

@@ -17,16 +17,17 @@
# limitations under the License.

RUNNER_HOME=$(dirname "$0")/..

RUNNER_LOGS_DIR=${RUNNER_LOGS_DIR:-${RUNNER_HOME}/logs}
RUNNER_DIR=${RUNNER_HOME%/bin*}
RUNNER_HEAP=${JAVA_HEAP:-4g}

JAVA_OPS="${JAVA_OPS} -Xmx${RUNNER_HEAP} -Xms${RUNNER_HEAP}"

[[ -d ${RUNNER_LOGS_DIR} ]] || mkdir -p ${RUNNER_LOGS_DIR}
APISIX_LISTEN_ADDRESS=${APISIX_LISTEN_ADDRESS}
APISIX_LISTEN_ADDRESS=${APISIX_LISTEN_ADDRESS#*:}
APISIX_HOME=${APISIX_LISTEN_ADDRESS%/conf*}

nohup java -jar ${JAVA_OPS} ${RUNNER_HOME}/apisxi-runner-start-*.jar \
1>${RUNNER_LOGS_DIR}/runner.out \
2>${RUNNER_LOGS_DIR}/runner.err &
nohup java -jar ${JAVA_OPS} ${RUNNER_DIR}/apisix-java-plugin-*.jar \
1>${APISIX_HOME}/logs/runner-out.log \
2>${APISIX_HOME}/logs/runner-err.log &

echo $! > ./logs/runner.pid
echo $! > ${APISIX_HOME}/logs/runner.pid
@@ -22,7 +22,6 @@
import io.github.api7.A6.HTTPReqCall.Rewrite;
import io.github.api7.A6.HTTPReqCall.Stop;
import io.github.api7.A6.TextEntry;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@@ -54,7 +53,7 @@ public class HttpResponse implements A6Response {

private String body;

private HttpResponseStatus status;
private Integer statusCode;

private A6ErrResponse errResponse;

@@ -87,7 +86,7 @@ public void setPath(String path) {
this.path = path;
}

public void setRespHeaders(String headerKey, String headerValue) {
public void setHeader(String headerKey, String headerValue) {
actionType = ActionType.Stop;
if (Objects.isNull(respHeaders)) {
respHeaders = new HashMap<>();
@@ -100,15 +99,16 @@ public void setBody(String body) {
this.body = body;
}

public void setStatus(HttpResponseStatus status) {
public void setStatusCode(int statusCode) {
actionType = ActionType.Stop;
this.status = status;
this.statusCode = statusCode;
}

public void setErrResponse(A6ErrResponse errResponse) {
this.errResponse = errResponse;
}

@Override
public A6ErrResponse getErrResponse() {
return this.errResponse;
}
@@ -162,8 +162,8 @@ private int buildStopResp(FlatBufferBuilder builder) {
}

Stop.startStop(builder);
if (!Objects.isNull(status)) {
Stop.addStatus(builder, status.code());
if (!Objects.isNull(statusCode)) {
Stop.addStatus(builder, statusCode);
}
if (-1 != headerIndex) {
Stop.addHeaders(builder, headerIndex);
@@ -13,10 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

logging:
level:
root: debug

cache.config:
expired: 3610
capacity: 1000
expired: ${APISIX_CONF_EXPIRE_TIME}
capacity: 1000

socket:
file: ${APISIX_LISTEN_ADDRESS}
@@ -2,6 +2,7 @@

import io.netty.channel.unix.DomainSocketAddress;
import io.netty.channel.unix.DomainSocketChannel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import reactor.netty.Connection;
@@ -13,6 +14,12 @@
@DisplayName("Unix Domain Socket Listen Test")
class PluginRunnerApplicationTest {

@BeforeEach
void setUp() {
System.setProperty("APISIX_LISTEN_ADDRESS", "unix:/tmp/runner.sock");
System.setProperty("APISIX_CONF_EXPIRE_TIME", String.valueOf(3600));
}

@Test
void testMain() {
PluginRunnerApplication.main(new String[]{"args"});
@@ -41,5 +41,13 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.apisix.plugin.runner.filter;

import org.apache.apisix.plugin.runner.HttpRequest;
import org.apache.apisix.plugin.runner.HttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class RewriteRequestDemoFilter implements PluginFilter {

@Override
public String name() {
/* It is recommended to keep the name of the filter the same as the class name.
Configure the filter to be executed on apisix's routes in the following format
{
"uri": "/hello",
"plugins": {
"ext-plugin-pre-req": {
"conf": [{
"name": "RewriteRequestDemoFilter",
"value": "bar"
}]
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
}
}
The value of name in the configuration corresponds to the value of return here.
*/

return "RewriteRequestDemoFilter";
}

@Override
public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
// note: the path to the rewrite must start with '/'
request.setPath("/get");
request.setHeader("new-header", "header_by_runner");
/* note: The value of the parameter is currently a string type.
If you need the json type, you need the upstream service to parse the string value to json.
For example, if the arg is set as follows
request.setArg("new arg", "{\"key1\":\"value1\",\"key2\":2}");
The arg received by the upstream service will be as follows
"new arg": "{\"key1\":\"value1\",\"key2\":2}"
*/
request.setArg("new arg", "{\"key1\":\"value1\",\"key2\":2}");

return chain.filter(request, response);
}

@Override
public int getOrder() {
//The order of filter execution in runner is determined by the order here, the smaller the order number, the higher the execution order.
return 0;
}
}

0 comments on commit 732cd2d

Please sign in to comment.