diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
deleted file mode 100644
index 6320a134..00000000
--- a/.devcontainer/devcontainer.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "starlight-devcontainer",
- "image": "mcr.microsoft.com/devcontainers/base:ubuntu", // Any generic, debian-based image.
- "features": {
- "ghcr.io/devcontainers/features/java:1": {
- "version": "8",
- "jdkDistro": "tem",
- "installMaven": true
- }
- }
-}
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index ded0100f..00000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: ''
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index bbcbbe7d..00000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ''
-labels: ''
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 0886c3e8..00000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-version: 2
-updates:
- - package-ecosystem: "maven"
- directory: "/starlight"
- schedule:
- interval: "weekly"
- - package-ecosystem: "maven"
- directory: "/spring-cloud-starter-baidu-starlight"
- schedule:
- interval: "weekly"
\ No newline at end of file
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index 2cf8f8de..00000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: "CodeQL"
-
-on:
- push:
- branches: [ "main" ]
- pull_request:
- # The branches below must be a subset of the branches above
- branches: [ "main" ]
- schedule:
- - cron: '26 21 * * 1'
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-20.04
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: [ 'java' ]
- java: [ '8', '17' ]
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- - name: Setup Java JDK and Maven
- uses: actions/setup-java@v3
- with:
- distribution: 'temurin'
- java-version: ${{ matrix.java }}
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v3
- with:
- languages: ${{ matrix.language }}
-
- - name: Cache local Maven repository
- uses: actions/cache@v3
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
- - name: Build starlight
- if: ${{ matrix.java == '8' }}
- run: |
- mvn -B clean package -Dmaven.test.skip --file starlight/pom.xml --no-transfer-progress
-
- - name: Build starlight-starter
- if: ${{ matrix.java == '17' }}
- run: |
- mvn -B clean package -Dmaven.test.skip --file spring-cloud-starter-baidu-starlight/pom.xml --no-transfer-progress
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
- with:
- category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml
deleted file mode 100644
index 9bd426ea..00000000
--- a/.github/workflows/snapshot.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-name: Snapshot
-on:
- push:
- branches: [ "main" ]
-
-jobs:
- build:
- runs-on: ubuntu-24.04
- strategy:
- fail-fast: false
- matrix:
- java: [ '8', '17' ]
- steps:
- - name: Checkout project
- uses: actions/checkout@v3
-
- - name: Setup Java JDK and Maven
- uses: actions/setup-java@v3
- with:
- distribution: 'temurin'
- java-version: ${{ matrix.java }}
- server-id: ossrh
- server-username: MAVEN_USERNAME
- server-password: MAVEN_PASSWORD
- gpg-private-key: ${{ secrets.GPG_SECRET }}
- gpg-passphrase: MAVEN_GPG_PASSPHRASE
-
- - name: Configure Git user
- run: |
- git config user.email "actions@github.com"
- git config user.name "GitHub Actions"
-
- - name: Cache local Maven repository
- uses: actions/cache@v3
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
- - name: Publish Snaphot package starlight
- if: ${{ matrix.java == '8' }}
- run: |
- mvn -B deploy -P deploy -Dmaven.test.skip --file starlight/pom.xml --no-transfer-progress
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- MAVEN_USERNAME: ${{ secrets.OSSRH_USER }}
- MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWD }}
- MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSWD }}
-
- - name: Publish Snaphot package starlight-starter
- if: ${{ matrix.java == '17' }}
- run: |
- mvn -B deploy -P deploy -Dmaven.test.skip --file spring-cloud-starter-baidu-starlight/pom.xml --no-transfer-progress
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- MAVEN_USERNAME: ${{ secrets.OSSRH_USER }}
- MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWD }}
- MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSWD }}
diff --git a/.github/workflows/starlight-release.yml b/.github/workflows/starlight-release.yml
deleted file mode 100644
index 038a2808..00000000
--- a/.github/workflows/starlight-release.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Starlight Release
-
-on:
- workflow_dispatch:
- inputs:
- releaseVersion:
- description: "Default version to use when preparing a release."
- required: true
- default: "X.Y.Z"
- developmentVersion:
- description: "Default version to use for new local working copy."
- required: true
- default: "X.Y.Z-SNAPSHOT"
-
-
-jobs:
- build:
- runs-on: ubuntu-24.04
- steps:
- - name: Checkout project
- uses: actions/checkout@v3
-
- - name: Setup Java JDK and Maven
- uses: actions/setup-java@v3
- with:
- distribution: 'temurin'
- java-version: '8'
- server-id: ossrh
- server-username: MAVEN_USERNAME
- server-password: MAVEN_PASSWORD
- gpg-private-key: ${{ secrets.GPG_SECRET }}
- gpg-passphrase: MAVEN_GPG_PASSPHRASE
-
- - name: Configure Git user
- run: |
- git config user.email "actions@github.com"
- git config user.name "GitHub Actions"
-
- - name: Publish package
- run: |
- cd starlight
- mvn -B release:prepare release:perform -P deploy -Darguments="-Dmaven.test.skip" -DreleaseVersion=${{ github.event.inputs.releaseVersion }} -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} --no-transfer-progress
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- MAVEN_USERNAME: ${{ secrets.OSSRH_USER }}
- MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWD }}
- MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSWD }}
-
- - name: Generate changelog
- id: changelog
- uses: metcalfc/changelog-generator@v4.0.1
- with:
- myToken: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Create GitHub Release
- id: create_release
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: starlight-v${{ github.event.inputs.releaseVersion }}
- release_name: starlight-v${{ github.event.inputs.releaseVersion }}
- body: |
- ### Things that changed in this release
- ${{ steps.changelog.outputs.changelog }}
- draft: true
- prerelease: false
diff --git a/.github/workflows/starlight-starter-release.yml b/.github/workflows/starlight-starter-release.yml
deleted file mode 100644
index b4040af1..00000000
--- a/.github/workflows/starlight-starter-release.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Starlight Starter Release
-
-on:
- workflow_dispatch:
- inputs:
- releaseVersion:
- description: "Default version to use when preparing a release."
- required: true
- default: "X.Y.Z"
- developmentVersion:
- description: "Default version to use for new local working copy."
- required: true
- default: "X.Y.Z-SNAPSHOT"
-
-
-jobs:
- build:
- runs-on: ubuntu-24.04
- steps:
- - name: Checkout project
- uses: actions/checkout@v3
-
- - name: Setup Java JDK and Maven
- uses: actions/setup-java@v3
- with:
- distribution: 'temurin'
- java-version: '17'
- server-id: ossrh
- server-username: MAVEN_USERNAME
- server-password: MAVEN_PASSWORD
- gpg-private-key: ${{ secrets.GPG_SECRET }}
- gpg-passphrase: MAVEN_GPG_PASSPHRASE
-
- - name: Configure Git user
- run: |
- git config user.email "actions@github.com"
- git config user.name "GitHub Actions"
-
- - name: Publish package
- run: |
- cd spring-cloud-starter-baidu-starlight
- mvn -B release:prepare release:perform -P deploy -Darguments="-Dmaven.test.skip" -DreleaseVersion=${{ github.event.inputs.releaseVersion }} -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} --no-transfer-progress
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- MAVEN_USERNAME: ${{ secrets.OSSRH_USER }}
- MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWD }}
- MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSWD }}
-
- - name: Generate changelog
- id: changelog
- uses: metcalfc/changelog-generator@v4.0.1
- with:
- myToken: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Create GitHub Release
- id: create_release
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: starter-v${{ github.event.inputs.releaseVersion }}
- release_name: starter-v${{ github.event.inputs.releaseVersion }}
- body: |
- ### Things that changed in this release
- ${{ steps.changelog.outputs.changelog }}
- draft: true
- prerelease: false
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
deleted file mode 100644
index 5ec129c7..00000000
--- a/.github/workflows/unit-test.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-name: Unit Test
-
-on:
- push:
- branches: [ "main" ]
- pull_request:
- branches: [ "main" ]
-
-jobs:
- build:
- runs-on: ubuntu-24.04
- strategy:
- fail-fast: false
- matrix:
- java: [ '8', '17' ]
- steps:
- - name: Checkout project
- uses: actions/checkout@v3
-
- - name: Setup Java JDK and Maven
- uses: actions/setup-java@v3
- with:
- distribution: 'temurin'
- java-version: ${{ matrix.java }}
-
- - name: Cache local Maven repository
- uses: actions/cache@v3
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
- - name: Build starlight
- if: ${{ matrix.java == '8' }}
- run: |
- mvn -B verify --file starlight/pom.xml --no-transfer-progress
-
- - name: Build starlight-starter
- if: ${{ matrix.java == '17' }}
- run: |
- mvn -B verify --file spring-cloud-starter-baidu-starlight/pom.xml --no-transfer-progress
-
- - name: Upload Coverage Report
- uses: codecov/codecov-action@v3
diff --git a/.gitignore b/.gitignore
index 037b7b73..b6d91100 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,86 +1,26 @@
-##############################
-## Java
-##############################
-.mtj.tmp/
-*.class
-*.jar
-*.war
-*.ear
-*.nar
-hs_err_pid*
-
-##############################
-## Maven
-##############################
-target/
-pom.xml.tag
-pom.xml.releaseBackup
-pom.xml.versionsBackup
-pom.xml.next
-pom.xml.bak
-release.properties
-dependency-reduced-pom.xml
-buildNumber.properties
-.mvn/timing.properties
-.mvn/wrapper/maven-wrapper.jar
-
-##############################
-## Gradle
-##############################
-bin/
-build/
-.gradle
-.gradletasknamecache
-gradle-app.setting
-!gradle-wrapper.jar
-
-##############################
-## IntelliJ
-##############################
-out/
-.idea/
-.idea_modules/
-*.iml
-*.ipr
-*.iws
-
-##############################
-## Eclipse
-##############################
-.settings/
-bin/
-tmp/
-.metadata
+*.swp
+*.swo
+target
.classpath
.project
-*.tmp
-*.bak
-*.swp
-*~.nib
-local.properties
-.loadpath
-.factorypath
-
-##############################
-## NetBeans
-##############################
-nbproject/private/
-build/
-nbbuild/
-dist/
-nbdist/
-nbactions.xml
-nb-configuration.xml
-
-##############################
-## Visual Studio Code
-##############################
-.vscode/
-.code-workspace
-
-##############################
-## OS X
-##############################
+.settings
+.svn
+output
+.idea
+*.iml
+*.pyc
+logs/
+*MANIFEST
+*artifacts
.DS_Store
-
-.sdkmanrc
\ No newline at end of file
+BaiduRpcErrno.java
+Options.java
+StreamingRpcProto.java
+SofaRpcProto.java
+BaiduRpcProto.java
+Echo.java
+HuluRpcProto.java
+brpc-java-core/.factorypath
+brpc-java-examples/.factorypath
+brpc-java-spring/.factorypath
+PublicPbrpcProto.java
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 00000000..1ef8d698
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed 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.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.4";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + " .jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..1914b842
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..05c741ea
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..5b3d2f67
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: java
+sudo: false
+
+cache:
+ directories:
+ - $HOME/.m2
+
+install: true
+
+script:
+ - travis_wait 30 ./mvnw clean install
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index 18c91471..00000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,128 +0,0 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, religion, or sexual identity
-and orientation.
-
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-## Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the
- overall community
-
-Examples of unacceptable behavior include:
-
-* The use of sexualized language or imagery, and sexual attention or
- advances of any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email
- address, without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official e-mail address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-.
-All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-## Enforcement Guidelines
-
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
-
-### 1. Correction
-
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
-
-### 2. Warning
-
-**Community Impact**: A violation through a single incident or series
-of actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or
-permanent ban.
-
-### 3. Temporary Ban
-
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction within
-the community.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.0, available at
-https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
-
-Community Impact Guidelines were inspired by [Mozilla's code of conduct
-enforcement ladder](https://github.com/mozilla/diversity).
-
-[homepage]: https://www.contributor-covenant.org
-
-For answers to common questions about this code of conduct, see the FAQ at
-https://www.contributor-covenant.org/faq. Translations are available at
-https://www.contributor-covenant.org/translations.
diff --git a/LICENSE b/LICENSE
index d52863d0..07a8a7e8 100644
--- a/LICENSE
+++ b/LICENSE
@@ -198,4 +198,4 @@
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.
\ No newline at end of file
+ limitations under the License.
diff --git a/README.md b/README.md
index fe6a957f..7eb046ba 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,117 @@
-[](https://github.com/baidu/starlight/blob/main/LICENSE)
-[](https://github.com/baidu/starlight/stargazers)
-[](https://github.com/baidu/starlight/issues)
-[](https://codecov.io/gh/baidu/starlight)
+[](https://travis-ci.org/baidu/brpc-java)
+
+
-| GroupId | ArtifactId | Snapshot Version | Stable Version |
-|---------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-|com.baidu.cloud| starlight-all | [](https://oss.sonatype.org/content/repositories/snapshots/com/baidu/cloud/starlight-all/) | [](https://repo.maven.apache.org/maven2/com/baidu/cloud/starlight-all/) |
-|com.baidu.cloud| spring-cloud-starter-baidu-starlight | [](https://oss.sonatype.org/content/repositories/snapshots/com/baidu/cloud/spring-cloud-starter-baidu-starlight/) | [](https://repo.maven.apache.org/maven2/com/baidu/cloud/spring-cloud-starter-baidu-starlight/) |
+# 项目名称
+brpc-java是baidu rpc的java版本实现,支持baidu rpc、nshead、sofa、hulu、http、stargate等协议。
-# Starlight: Lightweight Java RPC Framework
-Starlight 是一套面向云原生的微服务通信框架,兼容Spring生态,基于此可快速构建高效、稳定、可控、可观测的微服务应用,获得研发效率提升、业务稳定性增强等舒适体验。
-核心特性如下:
-* 多种协议支持:Starlight单端口支持[brpc](https://github.com/apache/incubator-brpc)、Spring MVC REST协议,提供超丰富的使用场景
-* 高性能远程通信:Starlight基于多路复用的NIO框架封装底层通信能力,提供高性能高并发网络通信能力
-* 易于使用:无需处理protobuf编译过程,通过原生Java接口和POJO对象加上类级别的注解,类似Java RMI和Spring MVC使用体验,即可实现brpc二进制协议的Server和Client;支持无损升级、异常实例摘除;规范化的日志可以秒级定位超时问题、序列化失败问题
+# 核心功能点
+* 支持baidu rpc标准协议、sofa协议、hulu协议、nshead+protobuf协议、http+protobuf/json协议、public pbrpc、stargate协议。
+* 支持SpringBoot starter,也支持SpringCloud的服务注册发现、用brpc-java替换Feign http调用,提升性能。
+* 支持Server Push机制,并支持扩展Server Push协议。
+* 支持多种naming服务,比如Zookeeper、Consul、List、File、DNS等,可以灵活扩展支持etcd、eureka、nacos等。
+* 支持多种负载均衡策略,比如fair、random、round robin、weight等。
+* 支持interceptor功能,支持计数器、令牌桶等server端限流算法。
+* rpc功能可独立使用,不是必须依赖Spring和注册中心功能。
+* 基于SPI机制可灵活扩展Protocol、NamingService和LoadBalance。
-详细使用和设计文档参见[Github Wiki](https://github.com/baidu/starlight/wiki)
+## 快速开始
+### 开发环境
+java 6+ && netty 4 && protobuf 2.5.0
-## JDK Requirements
-minimum JDK 8
+### 引入maven依赖
+非Spring环境:
+```xml
+
+ com.baidu
+ brpc-java
+ 2.5.8
+
+```
+Spring环境:
+```xml
+
+ com.baidu
+ brpc-spring
+ 2.5.8
+
+```
+SpringBoot环境:
+```xml
+
+ com.baidu
+ brpc-spring-boot-starter
+ 2.5.8
+
+```
+SpringCloud环境:
+```xml
+
+ com.baidu
+ spring-cloud-brpc
+ 2.5.8
+
+```
+Zookeeper注册中心:
+```xml
+
+ com.baidu
+ brpc-java-naming-zookeeper
+ 2.5.8
+
+```
+Consul注册中心:
+```xml
+
+ com.baidu
+ brpc-java-naming-consul
+ 2.5.8
+
+```
+### Server端使用
+* [server端基本用法](https://github.com/baidu/brpc-java/blob/master/docs/cn/server.md)
+* [搭建标准协议/sofa协议/hulu协议server](https://github.com/baidu/brpc-java/blob/master/docs/cn/brpc_server.md)
+* [搭建nshead server](https://github.com/baidu/brpc-java/blob/master/docs/cn/nshead_server.md)
+* [搭建http server](https://github.com/baidu/brpc-java/blob/master/docs/cn/http_server.md)
+* [server push 推送用法](https://github.com/baidu/brpc-java/blob/master/docs/cn/server_push.md)
-## License
-baidu/starlight is licensed under the Apache License 2.0
+### Client端使用
+* [client端基本用法](https://github.com/baidu/brpc-java/blob/master/docs/cn/client.md)
-
+### 与Spring集成
+* [Spring集成使用](https://github.com/baidu/brpc-java/blob/master/docs/cn/spring.md)
+
+### 扩展
+* [扩展Protocol、NamingService、LoadBalance](https://github.com/baidu/brpc-java/blob/master/docs/cn/extension.md)
+
+### 一些设计
+#### 网络模型
+采用netty的reactor网络模型,但跟常规用法有些不同:
+* 没有使用netty的ByteToMessageDecoder去解析协议,因为ByteToMessageDecoder内部会对buffer进行拷贝。
+* 为了提高并发,尽量少在IO线程中执行业务逻辑,所以在io线程中只会去解析协议的header部分,并把body的buffer retain出来,然后丢给工作线程去处理;工作线程会decode body,并执行具体业务逻辑。
+* 由于粘包/拆包问题,可能一次socket读操作会包含多个包,所以支持了批量往工作线程中submit任务。
+
+#### 零拷贝Buffer
+* [DynamicCompositeByteBuf](https://github.com/baidu/brpc-java/blob/master/docs/cn/composite_buffer.md)
+
+#### 线程池ThreadPool
+* 调研过JDK的ThreadPoolExecutor、ConcurrentLinkedQueue以及Disruptor,最后使用更高性能的[ThreadPool](
+https://github.com/baidu/brpc-java/blob/master/brpc-java-core/src/main/java/com/baidu/brpc/utils/ThreadPool.java)。
+* ThreadPool内部把生产者队列、消费者队列分开,用两个锁去控制同步,当consumer queue为空时,且producer queue不为空条件满足时,会交换两个队列。
+
+#### 比ConcurrentHashMap更快的FastFutureStore
+* [FastFutureStore](https://github.com/baidu/brpc-java/blob/master/docs/cn/fastfuturestore.md)
+
+## 压力测试数据
+### 部署环境:
+* Client/Server机器配置:cpu 12核,内存132G,千兆网卡。
+* [压测代码](https://github.com/baidu/brpc-java/blob/master/brpc-java-examples/src/main/java/com/baidu/brpc/example/standard/BenchmarkTest.java)
+### 压力测试结果:
+| 数据量 | 5 byte | 1k byte | 2k byte | 4k byte |
+|:-----:| :-----: | :-------: | :-------: | :-------: |
+|qps | 22w | 10w | 5.3w | 2.7w |
+
+# 微信交流群:
+
-# 微信交流群
-添加管理员olivaw2077帮忙加群,备注starlight
-
diff --git a/brpc-java-core/pom.xml b/brpc-java-core/pom.xml
new file mode 100644
index 00000000..1aaf30b5
--- /dev/null
+++ b/brpc-java-core/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ 4.0.0
+
+
+ com.baidu
+ brpc-java-parent
+ 2.5.9
+
+
+ brpc-java
+ jar
+
+ ${project.artifactId}
+ http://maven.apache.org
+ Java implementation for BRPC
+
+
+ UTF-8
+
+
+
+
+ io.netty
+ netty-all
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+ com.googlecode.protobuf-java-format
+ protobuf-java-format
+
+
+ com.baidu
+ jprotobuf
+
+
+ org.slf4j
+ slf4j-api
+
+
+ cglib
+ cglib
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.apache.commons
+ commons-collections4
+
+
+ org.apache.commons
+ commons-pool2
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+ com.google.code.gson
+ gson
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+ org.xerial.snappy
+ snappy-java
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ test
+
+
+ org.apache.logging.log4j
+ log4j-core
+ test
+
+
+ junit
+ junit
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+ com.dyuproject.protostuff
+ protostuff-runtime
+ 1.0.7
+
+
+ com.dyuproject.protostuff
+ protostuff-api
+ 1.0.7
+
+
+ com.dyuproject.protostuff
+ protostuff-collectionschema
+ 1.0.7
+
+
+ com.dyuproject.protostuff
+ protostuff-core
+ 1.0.7
+
+
+
+
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/ChannelInfo.java b/brpc-java-core/src/main/java/com/baidu/brpc/ChannelInfo.java
new file mode 100644
index 00000000..818f9d67
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/ChannelInfo.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc;
+
+import com.baidu.brpc.buffer.DynamicCompositeByteBuf;
+import com.baidu.brpc.client.FastFutureStore;
+import com.baidu.brpc.client.RpcFuture;
+import com.baidu.brpc.client.channel.BrpcChannel;
+import com.baidu.brpc.client.channel.ChannelType;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.protocol.Protocol;
+import com.baidu.brpc.protocol.Response;
+
+import io.netty.channel.Channel;
+import io.netty.util.Attribute;
+import io.netty.util.AttributeKey;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+@Setter
+@Getter
+@Slf4j
+public class ChannelInfo {
+ private static final AttributeKey CLIENT_CHANNEL_KEY = AttributeKey.valueOf("client_key");
+ private static final AttributeKey SERVER_CHANNEL_KEY = AttributeKey.valueOf("server_key");
+
+ private Channel channel;
+ private BrpcChannel channelGroup;
+ private Protocol protocol;
+ private long correlationId;
+ private FastFutureStore pendingRpc;
+ private DynamicCompositeByteBuf recvBuf = new DynamicCompositeByteBuf(16);
+
+ public void setProtocol(Protocol protocol) {
+ this.protocol = protocol;
+ }
+
+ public static ChannelInfo getOrCreateClientChannelInfo(Channel channel) {
+ Attribute attribute = channel.attr(ChannelInfo.CLIENT_CHANNEL_KEY);
+ ChannelInfo channelInfo = attribute.get();
+ if (channelInfo == null) {
+ channelInfo = new ChannelInfo();
+ // 此时FastFutureStore单例对象已经在RpcClient创建时初始化过了
+ channelInfo.setPendingRpc(FastFutureStore.getInstance(0));
+ channelInfo.setChannel(channel);
+ attribute.set(channelInfo);
+ }
+ return channelInfo;
+ }
+
+ public static ChannelInfo getClientChannelInfo(Channel channel) {
+ Attribute attribute = channel.attr(ChannelInfo.CLIENT_CHANNEL_KEY);
+ return attribute.get();
+ }
+
+ public static ChannelInfo getOrCreateServerChannelInfo(Channel channel) {
+ Attribute attribute = channel.attr(ChannelInfo.SERVER_CHANNEL_KEY);
+ ChannelInfo channelInfo = attribute.get();
+ if (channelInfo == null) {
+ channelInfo = new ChannelInfo();
+ channelInfo.setChannel(channel);
+ attribute.set(channelInfo);
+ }
+ return channelInfo;
+ }
+
+ public static ChannelInfo getServerChannelInfo(Channel channel) {
+ Attribute attribute = channel.attr(ChannelInfo.SERVER_CHANNEL_KEY);
+ return attribute.get();
+ }
+
+ public long addRpcFuture(RpcFuture future) {
+ // FastFutureStore会保证返回的logId不会占用已经使用过的slot
+ return pendingRpc.put(future);
+ }
+
+ public RpcFuture getRpcFuture(long correlationId) {
+ return pendingRpc.get(correlationId);
+ }
+
+ public RpcFuture removeRpcFuture(long correlationId) {
+ return pendingRpc.getAndRemove(correlationId);
+ }
+
+ /**
+ * return channel when fail
+ *
+ * @param channelType
+ */
+ public void handleRequestFail(ChannelType channelType, long correlationId) {
+ removeRpcFuture(correlationId);
+ if (channelType == ChannelType.SHORT_CONNECTION) {
+ channel.close();
+ channelGroup.close();
+ } else {
+ channelGroup.incFailedNum();
+ returnChannelAfterRequest(channelType);
+ }
+ }
+
+ /**
+ * return channel when success
+ */
+ public void handleRequestSuccess(ChannelType channelType) {
+ returnChannelAfterRequest(channelType);
+ }
+
+ private void returnChannelAfterRequest(ChannelType channelType) {
+ if (channelType != ChannelType.SHORT_CONNECTION && protocol.returnChannelBeforeResponse()) {
+ channelGroup.returnChannel(channel);
+ }
+ }
+
+ /**
+ * return channel when fail
+ */
+ public void handleResponseFail() {
+ channelGroup.incFailedNum();
+ returnChannelAfterResponse();
+ }
+
+ /**
+ * return channel when success
+ */
+ public void handleResponseSuccess() {
+ returnChannelAfterResponse();
+ }
+
+ private void returnChannelAfterResponse() {
+ if (!protocol.returnChannelBeforeResponse()) {
+ channelGroup.returnChannel(channel);
+ }
+ }
+
+ /**
+ * channel不可用时或者handler出现异常时处理逻辑
+ */
+ public void handleChannelException(RpcException ex) {
+ if (channelGroup != null) {
+ channelGroup.removeChannel(channel);
+ }
+ // 遍历并删除当前channel下所有RpcFuture
+ pendingRpc.traverse(new ChannelErrorStoreWalker(channel, ex));
+ }
+
+ public void close() {
+ log.debug("close the channel:{}", channel);
+ channel.close();
+ }
+
+ protected ChannelInfo() {
+ }
+
+ /**
+ * 用于遍历FutureStore元素的实现类
+ */
+ private static class ChannelErrorStoreWalker implements FastFutureStore.StoreWalker {
+ private Channel currentChannel;
+ private RpcException exception;
+
+ public ChannelErrorStoreWalker(Channel currentChannel, RpcException exception) {
+ this.currentChannel = currentChannel;
+ this.exception = exception;
+ }
+
+ @Override
+ public boolean visitElement(RpcFuture fut) {
+ // 与当前channel相同则删除
+ ChannelInfo chanInfo = fut.getChannelInfo();
+ if (null == chanInfo) {
+ return true;
+ }
+
+ // 不删除返回true
+ return currentChannel != chanInfo.channel;
+ }
+
+ @Override
+ public void actionAfterDelete(RpcFuture fut) {
+ Response response = fut.getRpcClient().getProtocol().createResponse();
+ response.setException(exception);
+ response.setRpcFuture(fut);
+ fut.handleResponse(response);
+ }
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/JprotobufRpcMethodInfo.java b/brpc-java-core/src/main/java/com/baidu/brpc/JprotobufRpcMethodInfo.java
new file mode 100644
index 00000000..1d25ae25
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/JprotobufRpcMethodInfo.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc;
+
+import com.baidu.bjf.remoting.protobuf.Codec;
+import com.baidu.bjf.remoting.protobuf.ProtobufProxy;
+import com.google.protobuf.CodedOutputStream;
+import com.baidu.brpc.buffer.DynamicCompositeByteBuf;
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * jprotobuf method info, which can be encode/decode jprotobuf class.
+ * details for jprotobuf: https://github.com/jhunters/jprotobuf
+ */
+@Setter
+@Getter
+public class JprotobufRpcMethodInfo extends RpcMethodInfo {
+ private Codec inputCodec;
+ private Codec outputCodec;
+
+ public JprotobufRpcMethodInfo(Method method) {
+ super(method);
+ inputCodec = ProtobufProxy.create((Class) (inputClasses[0]));
+ outputCodec = ProtobufProxy.create((Class) outputClass);
+ }
+
+ @Override
+ public byte[] inputEncode(Object input) throws IOException {
+ if (inputCodec != null) {
+ return inputCodec.encode(input);
+ }
+ return null;
+ }
+
+ @Override
+ public void inputWriteToStream(Object input, CodedOutputStream stream) throws IOException {
+ if (inputCodec != null) {
+ inputCodec.writeTo(input, stream);
+ }
+ }
+
+ @Override
+ public Object outputDecode(byte[] output) throws IOException {
+ if (outputCodec != null) {
+ return outputCodec.decode(output);
+ }
+ return null;
+ }
+
+ @Override
+ public Object outputDecode(ByteBuf output) throws IOException {
+ if (outputCodec != null) {
+ int len = output.readableBytes();
+ byte[] bytes = new byte[len];
+ output.readBytes(bytes);
+ return outputCodec.decode(bytes);
+ }
+ return null;
+ }
+
+ @Override
+ public Object outputDecode(DynamicCompositeByteBuf output) throws IOException {
+ if (outputCodec != null) {
+ int len = output.readableBytes();
+ byte[] bytes = new byte[len];
+ output.readBytes(bytes);
+ return outputCodec.decode(bytes);
+ }
+ return null;
+ }
+
+ @Override
+ public Object inputDecode(byte[] input) throws IOException {
+ if (inputCodec != null) {
+ return inputCodec.decode(input);
+ }
+ return null;
+ }
+
+ @Override
+ public Object inputDecode(ByteBuf input) throws IOException {
+ if (inputCodec != null) {
+ int len = input.readableBytes();
+ byte[] bytes = new byte[len];
+ input.readBytes(bytes);
+ return inputCodec.decode(bytes);
+ }
+ return null;
+ }
+
+ @Override
+ public Object inputDecode(DynamicCompositeByteBuf input) throws IOException {
+ if (inputCodec != null) {
+ int len = input.readableBytes();
+ byte[] bytes = new byte[len];
+ input.readBytes(bytes);
+ return inputCodec.decode(bytes);
+ }
+ return null;
+ }
+
+ @Override
+ public byte[] outputEncode(Object output) throws IOException {
+ if (outputCodec != null) {
+ return outputCodec.encode(output);
+ }
+ return null;
+ }
+
+ @Override
+ public void outputWriteToStream(Object output, CodedOutputStream stream) throws IOException {
+ if (outputCodec != null) {
+ outputCodec.writeTo(output, stream);
+ }
+ }
+
+ @Override
+ public int getInputSerializedSize(Object input) throws IOException {
+ if (inputCodec != null) {
+ return inputCodec.size(input);
+ }
+ return 0;
+ }
+
+ @Override
+ public int getOutputSerializedSize(Object output) throws IOException {
+ if (outputCodec != null) {
+ return outputCodec.size(output);
+ }
+ return 0;
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/ProtobufRpcMethodInfo.java b/brpc-java-core/src/main/java/com/baidu/brpc/ProtobufRpcMethodInfo.java
new file mode 100644
index 00000000..3f1ee027
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/ProtobufRpcMethodInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc;
+
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.Message;
+import com.baidu.brpc.buffer.DynamicCompositeByteBuf;
+import com.baidu.brpc.utils.ProtobufUtils;
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+
+@Setter
+@Getter
+public class ProtobufRpcMethodInfo extends RpcMethodInfo {
+ private Message inputInstance;
+ private Method inputParseFromMethod;
+ private Method inputGetDefaultInstanceMethod;
+
+ private Message outputInstance;
+ private Method outputParseFromMethod;
+ private Method outputGetDefaultInstanceMethod;
+
+ public ProtobufRpcMethodInfo(Method method) {
+ super(method);
+ try {
+ this.inputGetDefaultInstanceMethod = ((Class) inputClasses[0]).getMethod("getDefaultInstance");
+ this.inputInstance = (Message) inputGetDefaultInstanceMethod.invoke(null);
+ this.inputParseFromMethod = ((Class) inputClasses[0]).getMethod("parseFrom", byte[].class);
+
+ this.outputGetDefaultInstanceMethod = ((Class) outputClass).getMethod("getDefaultInstance");
+ this.outputInstance = (Message) outputGetDefaultInstanceMethod.invoke(null);
+ this.outputParseFromMethod = ((Class) outputClass).getMethod("parseFrom", byte[].class);
+
+ } catch (Exception ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ }
+
+ @Override
+ public byte[] inputEncode(Object input) throws IOException {
+ if (input instanceof Message) {
+ return ((Message) input).toByteArray();
+ }
+ return null;
+ }
+
+ @Override
+ public void inputWriteToStream(Object input, CodedOutputStream stream) throws IOException {
+ if (input instanceof Message) {
+ ((Message) input).writeTo(stream);
+ stream.flush();
+ }
+ }
+
+ @Override
+ public Object outputDecode(byte[] output) throws IOException {
+ if (outputParseFromMethod != null && output != null) {
+ try {
+ return outputParseFromMethod.invoke(outputClass, output);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object outputDecode(ByteBuf output) throws IOException {
+ return ProtobufUtils.parseFrom(output, outputInstance);
+ }
+
+ @Override
+ public Object outputDecode(DynamicCompositeByteBuf output) throws IOException {
+ return ProtobufUtils.parseFrom(output, outputInstance);
+ }
+
+ public Object outputDecode(InputStream stream) throws IOException {
+ if (outputGetDefaultInstanceMethod != null && stream != null) {
+ try {
+ Message proto = (Message) outputGetDefaultInstanceMethod.invoke(null);
+ proto = proto.newBuilderForType().mergeFrom(stream).build();
+ return proto;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+
+ public Object inputDecode(byte[] input) throws IOException {
+ return inputInstance.getParserForType().parseFrom(input);
+ }
+
+ public Object inputDecode(byte[] input, int offset, int len) throws IOException {
+ return inputInstance.getParserForType().parseFrom(input, offset, len);
+ }
+
+ public Object inputDecode(ByteBuf input) throws IOException {
+ return ProtobufUtils.parseFrom(input, inputInstance);
+ }
+
+ public Object inputDecode(DynamicCompositeByteBuf input) throws IOException {
+ return ProtobufUtils.parseFrom(input, inputInstance);
+ }
+
+ public Object inputDecode(InputStream stream) throws IOException {
+ if (inputGetDefaultInstanceMethod != null && stream != null) {
+ try {
+ Message proto = (Message) inputGetDefaultInstanceMethod.invoke(null);
+ proto = proto.newBuilderForType().mergeFrom(stream).build();
+ return proto;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+ return null;
+ }
+
+ public byte[] outputEncode(Object output) throws IOException {
+ if (output instanceof Message) {
+ return ((Message) output).toByteArray();
+ }
+ return null;
+ }
+
+ @Override
+ public void outputWriteToStream(Object output, CodedOutputStream stream) throws IOException {
+ if (output instanceof Message) {
+ ((Message) output).writeTo(stream);
+ stream.flush();
+ }
+ }
+
+ @Override
+ public int getInputSerializedSize(Object input) throws IOException {
+ if (input instanceof Message) {
+ return ((Message) input).getSerializedSize();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getOutputSerializedSize(Object output) throws IOException {
+ if (output instanceof Message) {
+ return ((Message) output).getSerializedSize();
+ }
+ return 0;
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/RpcContext.java b/brpc-java-core/src/main/java/com/baidu/brpc/RpcContext.java
new file mode 100644
index 00000000..14b16f0c
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/RpcContext.java
@@ -0,0 +1,129 @@
+package com.baidu.brpc;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.util.concurrent.FastThreadLocal;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * runtime information which are not in Request/Response.
+ * it should be reset when begin new rpc.
+ * the requestBinaryAttachment should be released at server.
+ * the responseBinaryAttachment should be released at client.
+ */
+@Setter
+@Getter
+public class RpcContext {
+ private static final FastThreadLocal CURRENT_RPC_CONTEXT = new FastThreadLocal() {
+ @Override
+ protected RpcContext initialValue() {
+ return new RpcContext();
+ }
+ };
+
+ public static boolean isSet() {
+ return CURRENT_RPC_CONTEXT.isSet();
+ }
+
+ public static RpcContext getContext() {
+ return CURRENT_RPC_CONTEXT.get();
+ }
+
+ public static void removeContext() {
+ CURRENT_RPC_CONTEXT.remove();
+ }
+
+ private Integer readTimeoutMillis;
+
+ private Integer writeTimeoutMillis;
+
+ /**
+ * logId of protocol, application can set it.
+ */
+ private Integer logId;
+
+ /**
+ * set custom service instance tag,
+ * so that load balance can select instance with the tag.
+ */
+ private String serviceTag;
+
+ private Map requestKvAttachment;
+ private ByteBuf requestBinaryAttachment;
+
+ private Map responseKvAttachment;
+ private ByteBuf responseBinaryAttachment;
+
+ private Channel channel;
+
+ private SocketAddress remoteAddress;
+
+ public void reset() {
+ readTimeoutMillis = null;
+ writeTimeoutMillis = null;
+ logId = null;
+ requestKvAttachment = null;
+ requestBinaryAttachment = null;
+ responseBinaryAttachment = null;
+ responseKvAttachment = null;
+ channel = null;
+ remoteAddress = null;
+ serviceTag = null;
+ }
+
+ public void setRequestBinaryAttachment(ByteBuf byteBuf) {
+ this.requestBinaryAttachment = byteBuf == null ? null : Unpooled.wrappedBuffer(byteBuf);
+ }
+
+ public void setRequestBinaryAttachment(byte[] bytes) {
+ this.requestBinaryAttachment = bytes == null ? null : Unpooled.wrappedBuffer(bytes);
+ }
+
+ public void setRequestKvAttachment(String key, Object value) {
+ if (requestKvAttachment == null) {
+ requestKvAttachment = new HashMap();
+ }
+ requestKvAttachment.put(key, value);
+ }
+
+ public void setRequestKvAttachment(Map attachment) {
+ if (requestKvAttachment == null) {
+ requestKvAttachment = attachment;
+ } else {
+ requestKvAttachment.putAll(attachment);
+ }
+ }
+
+ public void setResponseKvAttachment(String key, Object value) {
+ if (responseKvAttachment == null) {
+ responseKvAttachment = new HashMap();
+ }
+ responseKvAttachment.put(key, value);
+ }
+
+ public void setResponseKvAttachment(Map attachment) {
+ if (responseKvAttachment == null) {
+ responseKvAttachment = attachment;
+ } else {
+ responseKvAttachment.putAll(attachment);
+ }
+ }
+
+ public String getRemoteHost() {
+ if (remoteAddress != null) {
+ InetSocketAddress remoteAddress = (InetSocketAddress) this.remoteAddress;
+ InetAddress address = remoteAddress.getAddress();
+ return address.getHostAddress();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/RpcMethodInfo.java b/brpc-java-core/src/main/java/com/baidu/brpc/RpcMethodInfo.java
new file mode 100644
index 00000000..b49fd990
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/RpcMethodInfo.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import com.baidu.brpc.buffer.DynamicCompositeByteBuf;
+import com.baidu.brpc.protocol.nshead.NSHeadMeta;
+import com.baidu.brpc.utils.RpcMetaUtils;
+import com.baidu.brpc.utils.ThreadPool;
+import com.google.protobuf.CodedOutputStream;
+
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * rpc method info is parsed when application initialized.
+ * it can be both used at client and server side.
+ */
+@Setter
+@Getter
+public class RpcMethodInfo {
+ protected Method method;
+ protected String serviceName;
+ protected String methodName;
+ protected Type[] inputClasses;
+ protected Type outputClass;
+ protected NSHeadMeta nsHeadMeta;
+ // instance of interface which method belongs to
+ protected Object target;
+ protected ThreadPool threadPool;
+
+ public RpcMethodInfo(Method method) {
+ RpcMetaUtils.RpcMetaInfo metaInfo = RpcMetaUtils.parseRpcMeta(method);
+ this.serviceName = metaInfo.getServiceName();
+ this.methodName = metaInfo.getMethodName();
+ this.method = method;
+ Type[] inputClasses = method.getGenericParameterTypes();
+ if (inputClasses.length < 0) {
+ throw new IllegalArgumentException("invalid params");
+ }
+ this.inputClasses = inputClasses;
+ this.outputClass = method.getGenericReturnType();
+ this.nsHeadMeta = method.getAnnotation(NSHeadMeta.class);
+ }
+
+ /**
+ * encode request at client inside
+ *
+ * @param input the input
+ * @return the byte[]
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public byte[] inputEncode(Object input) throws IOException {
+ return null;
+ }
+
+ /**
+ * encode request at client inside
+ * @param input request
+ * @param stream out buffer stream
+ * @throws IOException io exception
+ */
+ public void inputWriteToStream(Object input, CodedOutputStream stream) throws IOException {
+ }
+
+ /**
+ * decode response at client side
+ *
+ * @param output response byte array
+ * @return response proto object
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public Object outputDecode(byte[] output) throws IOException {
+ return null;
+ }
+
+ /**
+ * decode response at client side
+ * @param output response netty ByteBuf
+ * @return response proto object
+ * @throws IOException
+ */
+ public Object outputDecode(ByteBuf output) throws IOException {
+ return null;
+ }
+
+ public Object outputDecode(DynamicCompositeByteBuf output) throws IOException {
+ return null;
+ }
+
+ /**
+ * decode request at server side
+ * @param input request byte array
+ * @return request proto instance
+ * @throws IOException
+ */
+ public Object inputDecode(byte[] input) throws IOException {
+ return null;
+ }
+
+ /**
+ * decode request at server side
+ * @param input request netty {@link ByteBuf}
+ * @return request proto instance
+ * @throws IOException
+ */
+ public Object inputDecode(ByteBuf input) throws IOException {
+ return null;
+ }
+
+ public Object inputDecode(DynamicCompositeByteBuf input) throws IOException {
+ return null;
+ }
+
+ /**
+ * encode response proto instance at server side
+ * @param output response proto instance
+ * @return encoded byte array
+ * @throws IOException
+ */
+ public byte[] outputEncode(Object output) throws IOException {
+ return null;
+ }
+
+ /**
+ * encode response to stream at server side
+ * @param output response object
+ * @param stream output stream
+ * @throws IOException
+ */
+ public void outputWriteToStream(Object output, CodedOutputStream stream) throws IOException {
+ }
+
+ /**
+ * get serialized size of request proto instance
+ * @param input proto instance
+ * @return serialized size
+ * @throws IOException
+ */
+ public int getInputSerializedSize(Object input) throws IOException {
+ return 0;
+ }
+
+ /**
+ * get serialized size of response proto instance
+ * @param output response object
+ * @return serialized size
+ * @throws IOException
+ */
+ public int getOutputSerializedSize(Object output) throws IOException {
+ return 0;
+ }
+}
diff --git a/starlight/starlight-api/src/main/java/com/baidu/cloud/starlight/api/transport/buffer/DynamicCompositeByteBuf.java b/brpc-java-core/src/main/java/com/baidu/brpc/buffer/DynamicCompositeByteBuf.java
similarity index 75%
rename from starlight/starlight-api/src/main/java/com/baidu/cloud/starlight/api/transport/buffer/DynamicCompositeByteBuf.java
rename to brpc-java-core/src/main/java/com/baidu/brpc/buffer/DynamicCompositeByteBuf.java
index 2b415152..dba852e6 100644
--- a/starlight/starlight-api/src/main/java/com/baidu/cloud/starlight/api/transport/buffer/DynamicCompositeByteBuf.java
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/buffer/DynamicCompositeByteBuf.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Baidu, Inc. All Rights Reserved.
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,26 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.baidu.cloud.starlight.api.transport.buffer;
-import com.baidu.cloud.thirdparty.netty.buffer.ByteBuf;
-import com.baidu.cloud.thirdparty.netty.buffer.CompositeByteBuf;
-import com.baidu.cloud.thirdparty.netty.buffer.Unpooled;
-import com.baidu.cloud.thirdparty.netty.buffer.UnpooledByteBufAllocator;
+package com.baidu.brpc.buffer;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
import java.util.Iterator;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.buffer.UnpooledByteBufAllocator;
+
/**
- * Migrate from brpc-java, see https://github.com/baidu/brpc-java/blob/master/docs/cn/composite_buffer.md Dynamic
- * composite multi {@link ByteBuf}, and it can be read just like a normal {@link ByteBuf}. Be constructed by one or many
- * readable {@link ByteBuf}, reference count of these buffers is not retained, and are managed by
- * {@link DynamicCompositeByteBuf}. When dynamic composite byte buffer release, it will release every sub buffers. When
- * the readable size of a sub buffer is zero, it will be removed from dynamic composite byte buffer, and will be
- * release. Currently, DynamicCompositeByteBuf does not support to write bytes, because netty {@link ByteBuf} has
- * satisfied already. DynamicCompositeByteBuf is used to handle network sticking / unpacking, and can achieve zero copy.
+ * dynamically composite multi {@link ByteBuf}, and it can be read just like a normal {@link ByteBuf}.
+ * it can be constructed by one or many readable {@link ByteBuf},
+ * reference count of these buffers is not retained, and are managed by {@link DynamicCompositeByteBuf}.
+ * when dynamic composite byte buffer release, it will release every sub buffers.
+ * when the readable size of a sub buffer is zero, it will be removed from dynamic composite byte buffer,
+ * and will be release.
+ * Currently, DynamicCompositeByteBuf does not support to write bytes,
+ * because netty {@link ByteBuf} has satisfied already.
*/
public class DynamicCompositeByteBuf {
private ArrayDeque buffers;
@@ -50,7 +51,6 @@ public DynamicCompositeByteBuf(int capacity) {
/**
* construct a new {@link DynamicCompositeByteBuf} with netty {@link ByteBuf}.
- *
* @param buf input buf, readable size should be greater than zero.
*/
public DynamicCompositeByteBuf(ByteBuf buf) {
@@ -61,7 +61,6 @@ public DynamicCompositeByteBuf(ByteBuf buf) {
/**
* construct a new {@link DynamicCompositeByteBuf} with netty {@link ByteBuf} array.
- *
* @param bufs byte buffer array, readable size of each element should be greater than zero.
* @param offset offset at array
* @param len length for create the new {@link DynamicCompositeByteBuf}
@@ -92,7 +91,6 @@ public boolean hasArray() {
/**
* it not check hasArray for performance
- *
* @return the backing array if it exists. {@code null} otherwise.
*/
public byte[] array() throws UnsupportedOperationException {
@@ -104,8 +102,8 @@ public byte[] array() throws UnsupportedOperationException {
/**
* it not check hasArray for performance
- *
- * @return The offset within this buffer's array of the first element of the buffer
+ * @return The offset within this buffer's array of the first element
+ * of the buffer
*/
public int arrayOffset() throws UnsupportedOperationException {
if (hasArray()) {
@@ -126,9 +124,8 @@ public boolean isReadable() {
}
/**
- * convert {@link DynamicCompositeByteBuf} to netty {@link ByteBuf}, the reference count of its underlying buffers
- * are not increased.
- *
+ * convert {@link DynamicCompositeByteBuf} to netty {@link ByteBuf},
+ * the reference count of its underlying buffers are not increased.
* @return netty ByteBuf
*/
public ByteBuf nettyByteBuf() {
@@ -139,13 +136,13 @@ public ByteBuf nettyByteBuf() {
if (size == 1) {
return buffers.pop();
}
- return new CompositeByteBuf(UnpooledByteBufAllocator.DEFAULT, false, size, buffers.toArray(new ByteBuf[0]));
+ return new CompositeByteBuf(UnpooledByteBufAllocator.DEFAULT, false,
+ size, buffers.toArray(new ByteBuf[0]));
}
/**
- * add netty {@link ByteBuf} to {@link DynamicCompositeByteBuf}. the reference count of netty byte buffer will be
- * managed by {@link DynamicCompositeByteBuf}.
- *
+ * add netty {@link ByteBuf} to {@link DynamicCompositeByteBuf}.
+ * the reference count of netty byte buffer will be managed by {@link DynamicCompositeByteBuf}.
* @param buffer netty byte buffer
*/
public void addBuffer(ByteBuf buffer) {
@@ -159,14 +156,16 @@ public void addBuffer(ByteBuf buffer) {
}
/**
- * Returns a new {@link CompositeByteBuf} which slice the first {@code length} of this composite byte buffer and
- * increases the {@code readerIndex} by the size of the new slice (= {@code length}).
+ * Returns a new {@link CompositeByteBuf} which slice the first {@code length} of this
+ * composite byte buffer and increases the {@code readerIndex} by the size
+ * of the new slice (= {@code length}).
*
* @param length the size of the new composite byte buffer
*
* @return the newly created composite byte buffer
*
- * @throws IndexOutOfBoundsException if {@code length} is greater than {@code this.readableBytes}
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
*/
public ByteBuf readRetainedSlice(int length) {
if (length > readableBytes) {
@@ -228,15 +227,16 @@ public ByteBuf readRetainedSlice(int length) {
}
/**
- * Returns a new {@link CompositeByteBuf} which slice the first {@code length} of this composite byte buffer while
- * they maintain separate indexes and marks. This method does not modify {@code readerIndex} or {@code writerIndex}
- * of this buffer.
+ * Returns a new {@link CompositeByteBuf} which slice the first {@code length} of this
+ * composite byte buffer while they maintain separate indexes and marks.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of this buffer.
*
* @param length the size of the new composite byte buffer
*
* @return the newly created composite byte buffer
*
- * @throws IndexOutOfBoundsException if {@code length} is greater than {@code this.readableBytes}
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
*/
public ByteBuf retainedSlice(int length) {
if (length > readableBytes) {
@@ -317,21 +317,24 @@ public DynamicCompositeByteBuf readBytes(byte[] dst) {
}
/**
- * Transfers this buffer's data to the specified destination starting at the current {@code readerIndex} and
- * increases the {@code readerIndex} by the number of the transferred bytes (= {@code length}).
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} and increases the {@code readerIndex}
+ * by the number of the transferred bytes (= {@code length}).
*
* @param dstIndex the first index of the destination
- * @param length the number of bytes to transfer
+ * @param length the number of bytes to transfer
*
- * @throws IndexOutOfBoundsException if the specified {@code dstIndex} is less than {@code 0}, if {@code length} is
- * greater than {@code this.readableBytes}, or if {@code dstIndex + length} is greater than
- * {@code dst.length}
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code dstIndex} is less than {@code 0},
+ * if {@code length} is greater than {@code this.readableBytes}, or
+ * if {@code dstIndex + length} is greater than {@code dst.length}
*/
public DynamicCompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) {
if (dst == null) {
throw new NullPointerException();
}
- if (dstIndex < 0 || dstIndex >= dst.length || dstIndex + length > dst.length || dstIndex + length < 0) {
+ if (dstIndex < 0 || dstIndex >= dst.length
+ || dstIndex + length > dst.length || dstIndex + length < 0) {
throw new IndexOutOfBoundsException();
}
@@ -354,9 +357,11 @@ public DynamicCompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) {
}
/**
- * Gets a byte at the current {@code readerIndex} and increases the {@code readerIndex} by {@code 1} in this buffer.
+ * Gets a byte at the current {@code readerIndex} and increases
+ * the {@code readerIndex} by {@code 1} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 1}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 1}
*/
public byte readByte() {
checkReadableBytes0(1);
@@ -370,20 +375,22 @@ public byte readByte() {
}
/**
- * Gets an unsigned byte at the current {@code readerIndex} and increases the {@code readerIndex} by {@code 1} in
- * this buffer.
+ * Gets an unsigned byte at the current {@code readerIndex} and increases
+ * the {@code readerIndex} by {@code 1} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 1}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 1}
*/
public short readUnsignedByte() {
return (short) (readByte() & 0xFF);
}
/**
- * Gets a 16-bit short integer at the current {@code readerIndex} and increases the {@code readerIndex} by {@code 2}
- * in this buffer.
+ * Gets a 16-bit short integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 2} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
*/
public short readShort() {
checkReadableBytes0(2);
@@ -405,10 +412,12 @@ public short readShort() {
}
/**
- * Gets a 16-bit short integer at the current {@code readerIndex} in the Little Endian Byte Order and increases the
- * {@code readerIndex} by {@code 2} in this buffer.
+ * Gets a 16-bit short integer at the current {@code readerIndex}
+ * in the Little Endian Byte Order and increases the {@code readerIndex}
+ * by {@code 2} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
*/
public short readShortLE() {
checkReadableBytes0(2);
@@ -429,30 +438,34 @@ public short readShortLE() {
}
/**
- * Gets an unsigned 16-bit short integer at the current {@code readerIndex} and increases the {@code readerIndex} by
- * {@code 2} in this buffer.
+ * Gets an unsigned 16-bit short integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 2} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
*/
public int readUnsignedShort() {
return readShort() & 0xFFFF;
}
/**
- * Gets an unsigned 16-bit short integer at the current {@code readerIndex} in the Little Endian Byte Order and
- * increases the {@code readerIndex} by {@code 2} in this buffer.
+ * Gets an unsigned 16-bit short integer at the current {@code readerIndex}
+ * in the Little Endian Byte Order and increases the {@code readerIndex}
+ * by {@code 2} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
*/
public int readUnsignedShortLE() {
return readShortLE() & 0xFFFF;
}
/**
- * Gets a 32-bit integer at the current {@code readerIndex} and increases the {@code readerIndex} by {@code 4} in
- * this buffer.
+ * Gets a 32-bit integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 4} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
*/
public int readInt() {
checkReadableBytes0(4);
@@ -473,10 +486,12 @@ public int readInt() {
}
/**
- * Gets a 32-bit integer at the current {@code readerIndex} in the Little Endian Byte Order and increases the
- * {@code readerIndex} by {@code 4} in this buffer.
+ * Gets a 32-bit integer at the current {@code readerIndex}
+ * in the Little Endian Byte Order and increases the {@code readerIndex}
+ * by {@code 4} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
*/
public int readIntLE() {
checkReadableBytes0(4);
@@ -497,10 +512,11 @@ public int readIntLE() {
}
/**
- * Gets a 64-bit integer at the current {@code readerIndex} and increases the {@code readerIndex} by {@code 8} in
- * this buffer.
+ * Gets a 64-bit integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 8} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 8}
*/
public long readLong() {
checkReadableBytes0(8);
@@ -521,10 +537,12 @@ public long readLong() {
}
/**
- * Gets a 64-bit integer at the current {@code readerIndex} in the Little Endian Byte Order and increases the
- * {@code readerIndex} by {@code 8} in this buffer.
+ * Gets a 64-bit integer at the current {@code readerIndex}
+ * in the Little Endian Byte Order and increases the {@code readerIndex}
+ * by {@code 8} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 8}
*/
public long readLongLE() {
checkReadableBytes0(8);
@@ -545,50 +563,57 @@ public long readLongLE() {
}
/**
- * Gets a 2-byte UTF-16 character at the current {@code readerIndex} and increases the {@code readerIndex} by
- * {@code 2} in this buffer.
+ * Gets a 2-byte UTF-16 character at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 2} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
*/
public char readChar() {
return (char) readShort();
}
/**
- * Gets a 32-bit floating point number at the current {@code readerIndex} and increases the {@code readerIndex} by
- * {@code 4} in this buffer.
+ * Gets a 32-bit floating point number at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 4} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
*/
public float readFloat() {
return Float.intBitsToFloat(readInt());
}
/**
- * Gets a 32-bit floating point number at the current {@code readerIndex} in Little Endian Byte Order and increases
- * the {@code readerIndex} by {@code 4} in this buffer.
+ * Gets a 32-bit floating point number at the current {@code readerIndex}
+ * in Little Endian Byte Order and increases the {@code readerIndex}
+ * by {@code 4} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
*/
public float readFloatLE() {
return Float.intBitsToFloat(readIntLE());
}
/**
- * Gets a 64-bit floating point number at the current {@code readerIndex} and increases the {@code readerIndex} by
- * {@code 8} in this buffer.
+ * Gets a 64-bit floating point number at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 8} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 8}
*/
public double readDouble() {
return Double.longBitsToDouble(readLong());
}
/**
- * Gets a 64-bit floating point number at the current {@code readerIndex} in Little Endian Byte Order and increases
- * the {@code readerIndex} by {@code 8} in this buffer.
+ * Gets a 64-bit floating point number at the current {@code readerIndex}
+ * in Little Endian Byte Order and increases the {@code readerIndex}
+ * by {@code 8} in this buffer.
*
- * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8}
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 8}
*/
public double readDoubleLE() {
return Double.longBitsToDouble(readLongLE());
@@ -600,8 +625,9 @@ public ByteOrder order() {
private void checkReadableBytes0(int length) {
if (readableBytes < length) {
- throw new IndexOutOfBoundsException(
- String.format("length(%d) exceeds readableBytes(%d): %s", length, readableBytes, this));
+ throw new IndexOutOfBoundsException(String.format(
+ "length(%d) exceeds readableBytes(%d): %s",
+ length, readableBytes, this));
}
}
@@ -626,6 +652,7 @@ public void release() {
}
}
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -642,4 +669,4 @@ public String toString() {
return sb.toString();
}
-}
\ No newline at end of file
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/buffer/DynamicCompositeByteBufInputStream.java b/brpc-java-core/src/main/java/com/baidu/brpc/buffer/DynamicCompositeByteBufInputStream.java
new file mode 100644
index 00000000..26fb47f9
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/buffer/DynamicCompositeByteBufInputStream.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.buffer;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.DataInput;
+import java.io.DataInputStream;
+
+/**
+ * An {@link InputStream} which reads data from a {@link DynamicCompositeByteBuf}.
+ *
+ * A read operation against this stream will occur at the {@code readerIndex}
+ * of its underlying buffer and the {@code readerIndex} will increase during
+ * the read operation. Please note that it only reads up to the number of
+ * readable bytes of its underlying buffer.
+ *
+ * This stream implements {@link DataInput} for your convenience.
+ *
+ */
+public class DynamicCompositeByteBufInputStream extends InputStream implements DataInput {
+ private DynamicCompositeByteBuf buffer;
+ private boolean closed;
+ private boolean releaseOnClose;
+
+ /**
+ * Creates a new stream which reads data from the specified {@code buffer}
+ * @param compositeByteBuf The buffer which provides the content for this {@link InputStream}.
+ */
+ public DynamicCompositeByteBufInputStream(DynamicCompositeByteBuf compositeByteBuf) {
+ this(compositeByteBuf, false);
+ }
+
+ /**
+ * Creates a new stream which reads data from the specified {@code buffer}
+ * @param buffer The buffer which provides the content for this {@link InputStream}.
+ * @param releaseOnClose {@code true} means that when {@link #close()} is called
+ * then {@link DynamicCompositeByteBuf#release()}
+ * will be called on {@code buffer}.
+ */
+ public DynamicCompositeByteBufInputStream(DynamicCompositeByteBuf buffer, boolean releaseOnClose) {
+ if (buffer == null) {
+ throw new NullPointerException("buffer");
+ }
+
+ this.releaseOnClose = releaseOnClose;
+ this.buffer = buffer;
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ } finally {
+ // The Closable interface says "If the stream is already closed then invoking this method has no effect."
+ if (releaseOnClose && !closed) {
+ closed = true;
+ buffer.release();
+ }
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ return buffer.readableBytes();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!buffer.isReadable()) {
+ return -1;
+ }
+ return buffer.readByte() & 0xff;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int available = available();
+ if (available == 0) {
+ return -1;
+ }
+
+ len = Math.min(available, len);
+ buffer.readBytes(b, off, len);
+ return len;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n > Integer.MAX_VALUE) {
+ return skipBytes(Integer.MAX_VALUE);
+ } else {
+ return skipBytes((int) n);
+ }
+ }
+
+ public int skipBytes(int n) throws IOException {
+ int nBytes = Math.min(available(), n);
+ buffer.skipBytes(nBytes);
+ return nBytes;
+ }
+
+ @Override
+ public boolean readBoolean() throws IOException {
+ checkAvailable(1);
+ return read() != 0;
+ }
+
+ @Override
+ public byte readByte() throws IOException {
+ if (!buffer.isReadable()) {
+ throw new EOFException();
+ }
+ return buffer.readByte();
+ }
+
+ @Override
+ public char readChar() throws IOException {
+ return (char) readShort();
+ }
+
+ @Override
+ public double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ @Override
+ public float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ @Override
+ public void readFully(byte[] b) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ @Override
+ public void readFully(byte[] b, int off, int len) throws IOException {
+ checkAvailable(len);
+ buffer.readBytes(b, off, len);
+ }
+
+ @Override
+ public int readInt() throws IOException {
+ checkAvailable(4);
+ return buffer.readInt();
+ }
+
+ private final StringBuilder lineBuf = new StringBuilder();
+
+ @Override
+ public String readLine() throws IOException {
+ lineBuf.setLength(0);
+
+ loop:
+ while (true) {
+ if (!buffer.isReadable()) {
+ return lineBuf.length() > 0 ? lineBuf.toString() : null;
+ }
+
+ int c = buffer.readUnsignedByte();
+ switch (c) {
+ case '\n':
+ break loop;
+
+ case '\r':
+ if (buffer.isReadable() && (char) buffer.readUnsignedByte() == '\n') {
+ buffer.skipBytes(1);
+ }
+ break loop;
+
+ default:
+ lineBuf.append((char) c);
+ }
+ }
+
+ return lineBuf.toString();
+ }
+
+ @Override
+ public long readLong() throws IOException {
+ checkAvailable(8);
+ return buffer.readLong();
+ }
+
+ @Override
+ public short readShort() throws IOException {
+ checkAvailable(2);
+ return buffer.readShort();
+ }
+
+ @Override
+ public String readUTF() throws IOException {
+ return DataInputStream.readUTF(this);
+ }
+
+ @Override
+ public int readUnsignedByte() throws IOException {
+ return readByte() & 0xff;
+ }
+
+ @Override
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ private void checkAvailable(int fieldSize) throws IOException {
+ if (fieldSize < 0) {
+ throw new IndexOutOfBoundsException("fieldSize cannot be a negative number");
+ }
+ if (fieldSize > available()) {
+ throw new EOFException("fieldSize is too long! Length is " + fieldSize
+ + ", but maximum is " + available());
+ }
+ }
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/AsyncAwareFuture.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/AsyncAwareFuture.java
new file mode 100644
index 00000000..e1cbdca6
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/AsyncAwareFuture.java
@@ -0,0 +1,10 @@
+package com.baidu.brpc.client;
+
+import java.util.concurrent.Future;
+
+/**
+ * Created by wanghongfei on 2019-04-19.
+ */
+public interface AsyncAwareFuture extends Future {
+ boolean isAsync();
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/BrpcProxy.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/BrpcProxy.java
new file mode 100644
index 00000000..aa50ebcb
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/BrpcProxy.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2019 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import com.baidu.brpc.JprotobufRpcMethodInfo;
+import com.baidu.brpc.ProtobufRpcMethodInfo;
+import com.baidu.brpc.RpcContext;
+import com.baidu.brpc.RpcMethodInfo;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.interceptor.DefaultInterceptorChain;
+import com.baidu.brpc.interceptor.Interceptor;
+import com.baidu.brpc.interceptor.InterceptorChain;
+import com.baidu.brpc.naming.NamingOptions;
+import com.baidu.brpc.protocol.Request;
+import com.baidu.brpc.protocol.Response;
+import com.baidu.brpc.protocol.nshead.NSHead;
+import com.baidu.brpc.protocol.nshead.NSHeadMeta;
+import com.baidu.brpc.protocol.push.SPHead;
+import com.baidu.brpc.protocol.push.ServerPushProtocol;
+import com.baidu.brpc.utils.ProtobufUtils;
+
+import lombok.extern.slf4j.Slf4j;
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+/**
+ * Created by huwenwei on 2017/4/25.
+ */
+@SuppressWarnings("unchecked")
+@Slf4j
+public class BrpcProxy implements MethodInterceptor {
+
+ private static final Set notProxyMethodSet = new HashSet();
+
+ static {
+ notProxyMethodSet.add("getClass");
+ notProxyMethodSet.add("hashCode");
+ notProxyMethodSet.add("equals");
+ notProxyMethodSet.add("clone");
+ notProxyMethodSet.add("toString");
+ notProxyMethodSet.add("notify");
+ notProxyMethodSet.add("notifyAll");
+ notProxyMethodSet.add("wait");
+ notProxyMethodSet.add("finalize");
+ }
+
+ private RpcClient rpcClient;
+
+ private Map rpcMethodMap = new HashMap();
+
+ /**
+ * 初始化时提前解析好method信息,在rpc交互时会更快。
+ *
+ * @param rpcClient rpc client对象
+ * @param clazz rpc接口类
+ */
+ protected BrpcProxy(RpcClient rpcClient, Class clazz) {
+ this.rpcClient = rpcClient;
+ Method[] methods = clazz.getMethods();
+ for (Method method : methods) {
+ if (notProxyMethodSet.contains(method.getName())) {
+ log.debug("{}:{} does not need to proxy",
+ method.getDeclaringClass().getName(), method.getName());
+ continue;
+ }
+
+ Class[] parameterTypes = method.getParameterTypes();
+ int paramLength = parameterTypes.length;
+ if (paramLength >= 1
+ && Future.class.isAssignableFrom(method.getReturnType())
+ && !RpcCallback.class.isAssignableFrom(parameterTypes[paramLength - 1])) {
+ throw new IllegalArgumentException("returnType is Future, but last argument is not RpcCallback");
+ }
+
+ Method syncMethod = method;
+ if (paramLength > 1) {
+ int startIndex = 0;
+ int endIndex = paramLength - 1;
+ // has callback, async rpc
+ if (RpcCallback.class.isAssignableFrom(parameterTypes[paramLength - 1])) {
+ endIndex--;
+ paramLength--;
+ }
+ Class[] actualParameterTypes = new Class[paramLength];
+ for (int i = 0; startIndex <= endIndex; i++) {
+ actualParameterTypes[i] = parameterTypes[startIndex++];
+ }
+ try {
+ syncMethod = method.getDeclaringClass().getMethod(
+ method.getName(), actualParameterTypes);
+ } catch (NoSuchMethodException ex) {
+ throw new IllegalArgumentException("can not find sync method:" + method.getName());
+ }
+ }
+
+ RpcMethodInfo methodInfo;
+ ProtobufUtils.MessageType messageType = ProtobufUtils.getMessageType(syncMethod);
+ if (messageType == ProtobufUtils.MessageType.PROTOBUF) {
+ methodInfo = new ProtobufRpcMethodInfo(syncMethod);
+ } else if (messageType == ProtobufUtils.MessageType.JPROTOBUF) {
+ methodInfo = new JprotobufRpcMethodInfo(syncMethod);
+ } else {
+ methodInfo = new RpcMethodInfo(syncMethod);
+ }
+
+ rpcMethodMap.put(method.getName(), methodInfo);
+ log.debug("client serviceName={}, methodName={}",
+ method.getDeclaringClass().getName(), method.getName());
+ }
+ }
+
+ public static T getProxy(RpcClient rpcClient, Class clazz) {
+ return getProxy(rpcClient, clazz, null);
+ }
+
+ public static T getProxy(RpcClient rpcClient, Class clazz, NamingOptions namingOptions) {
+ rpcClient.setServiceInterface(clazz, namingOptions);
+ rpcClient.getLoadBalanceInterceptor().setRpcClient(rpcClient);
+ rpcClient.getInterceptors().add(rpcClient.getLoadBalanceInterceptor());
+ Enhancer en = new Enhancer();
+ en.setSuperclass(clazz);
+ en.setCallback(new BrpcProxy(rpcClient, clazz));
+ return (T) en.create();
+ }
+
+ /**
+ * 调用用户接口时候, 实际执行的方法
+ *
+ * @param obj
+ * @param method
+ * @param args
+ * @param proxy
+ *
+ * @return
+ *
+ * @throws Throwable
+ */
+ @Override
+ public Object intercept(Object obj, Method method, Object[] args,
+ MethodProxy proxy) throws Throwable {
+ String methodName = method.getName();
+ RpcMethodInfo rpcMethodInfo = rpcMethodMap.get(methodName);
+ if (rpcMethodInfo == null) {
+ log.debug("{}:{} does not need to proxy",
+ method.getDeclaringClass().getName(), methodName);
+ return proxy.invokeSuper(obj, args);
+ }
+ Request request = null;
+ Response response = null;
+
+ List interceptors = null;
+ int readTimeout = 10 * 1000;
+ int writeTimeout = 10 * 1000;
+
+ interceptors = rpcClient.getInterceptors();
+ request = rpcClient.getProtocol().createRequest();
+ response = rpcClient.getProtocol().getResponse();
+ if (rpcClient.getProtocol() instanceof ServerPushProtocol) {
+ SPHead spHead = ((ServerPushProtocol) rpcClient.getProtocol()).createSPHead();
+ spHead.setType(SPHead.TYPE_REQUEST);
+ request.setSpHead(spHead);
+ }
+
+ request.setCompressType(rpcClient.getRpcClientOptions().getCompressType().getNumber());
+ request.setSubscribeInfo(rpcClient.getSubscribeInfo());
+ readTimeout = rpcClient.getRpcClientOptions().getReadTimeoutMillis();
+ writeTimeout = rpcClient.getRpcClientOptions().getWriteTimeoutMillis();
+
+ try {
+
+ request.setTarget(obj);
+ request.setRpcMethodInfo(rpcMethodInfo);
+ request.setTargetMethod(rpcMethodInfo.getMethod());
+ request.setServiceName(rpcMethodInfo.getServiceName());
+ request.setMethodName(rpcMethodInfo.getMethodName());
+ NSHeadMeta nsHeadMeta = rpcMethodInfo.getNsHeadMeta();
+ NSHead nsHead = nsHeadMeta == null ? new NSHead() : new NSHead(0, nsHeadMeta.id(), nsHeadMeta.version(),
+ nsHeadMeta.provider(), 0);
+ request.setNsHead(nsHead);
+
+ // parse request params
+ RpcCallback callback = null;
+ int argLength = args.length;
+ if (argLength > 1) {
+ int startIndex = 0;
+ int endIndex = argLength - 1;
+ // 异步调用
+ if (args[endIndex] instanceof RpcCallback) {
+ callback = (RpcCallback) args[endIndex];
+ endIndex -= 1;
+ argLength -= 1;
+ }
+
+ if (argLength <= 0) {
+ throw new RpcException(RpcException.UNKNOWN_EXCEPTION, "invalid params");
+ }
+
+ Object[] sendArgs = new Object[argLength];
+ for (int i = 0; startIndex <= endIndex; i++) {
+ sendArgs[i] = args[startIndex++];
+ }
+ request.setArgs(sendArgs);
+ request.setCallback(callback);
+ } else {
+ // sync call
+ request.setArgs(args);
+ }
+
+ if (RpcContext.isSet()) {
+ RpcContext rpcContext = RpcContext.getContext();
+ // attachment
+ if (rpcContext.getRequestKvAttachment() != null) {
+ request.setKvAttachment(rpcContext.getRequestKvAttachment());
+ }
+ if (rpcContext.getRequestBinaryAttachment() != null) {
+ request.setBinaryAttachment(rpcContext.getRequestBinaryAttachment());
+ }
+ if (rpcContext.getLogId() != null) {
+ request.getNsHead().logId = rpcContext.getLogId();
+ request.setLogId(rpcContext.getLogId());
+ }
+ if (rpcContext.getServiceTag() != null) {
+ request.setServiceTag(rpcContext.getServiceTag());
+ }
+ if (rpcContext.getReadTimeoutMillis() != null) {
+ request.setReadTimeoutMillis(rpcContext.getReadTimeoutMillis());
+ }
+ if (rpcContext.getWriteTimeoutMillis() != null) {
+ request.setWriteTimeoutMillis(rpcContext.getWriteTimeoutMillis());
+ }
+ rpcContext.reset();
+ }
+
+ if (request.getReadTimeoutMillis() == null) {
+ request.setReadTimeoutMillis(readTimeout);
+ }
+ if (request.getWriteTimeoutMillis() == null) {
+ request.setWriteTimeoutMillis(writeTimeout);
+ }
+
+ InterceptorChain interceptorChain = new DefaultInterceptorChain(interceptors);
+ try {
+ interceptorChain.intercept(request, response);
+ if (response.getException() != null) {
+ throw new RpcException(response.getException());
+ }
+ if (request.getCallback() != null) {
+ return response.getRpcFuture();
+ } else {
+ return response.getResult();
+ }
+ } catch (Exception ex) {
+ log.error("exception :", ex);
+ throw new RpcException(response.getException());
+ }
+ } finally {
+ if (request != null) {
+ // release send buffer because we retain send buffer when send request.
+ request.release();
+ }
+ }
+ }
+
+ public Map getRpcMethodMap() {
+ return rpcMethodMap;
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/FastFutureStore.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/FastFutureStore.java
new file mode 100644
index 00000000..291e7bd6
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/FastFutureStore.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A lock-free datastructure faster than #{@link java.util.concurrent.ConcurrentHashMap} in certain circumstance.
+ * When the underlying array runs out of space, FastFutureStore will downgrade to #{@link ConcurrentHashMap}
+ *
+ * Created by wanghongfei on 2018/11/19.
+ */
+public class FastFutureStore {
+ private static final Logger LOG = LoggerFactory.getLogger(FastFutureStore.class);
+
+ /**
+ * Default capacity of the internal array
+ */
+ private static final int DEFAULT_ARRAY_CAP = 10000;
+
+ private static volatile FastFutureStore singletonInstance;
+
+ /**
+ * Atomic array used to store #{@link RpcFuture}
+ */
+ private AtomicReferenceArray futArray;
+
+ /**
+ * A counter used to calculate array index
+ */
+ private AtomicLong slotCounter = new AtomicLong(0);
+
+ /**
+ * Capacity of the internal array
+ */
+ private int cap;
+
+ /**
+ * Downgrade to ConcurrentHashMap when array is exhausted
+ */
+ private Map backupMap = new ConcurrentHashMap();
+
+ /**
+ * The boundary between array-stored id and map-stored id.
+ * Binary format: 0100 0000 0000 0000 ... ... 0000(64 bits).
+ *
+ * Ids less than(01xx xxxx ... xxx) this value should be stored in array.
+ * Ids greater than(01xx xxxx ... xxx) this value should be stored in map.
+ */
+ private static long COUNTER_VALUE_BOUNDARY = (long) 0x1 << 62;
+
+ /**
+ * @param cap capacity of the internal array
+ */
+ public FastFutureStore(int cap) {
+ if (cap < 1) {
+ cap = DEFAULT_ARRAY_CAP;
+ }
+
+ this.cap = cap;
+ this.futArray = new AtomicReferenceArray(cap);
+ }
+
+ /**
+ * Obtain singleton object
+ *
+ * @param cap Capacity of the internal array
+ *
+ * @return The singleton instance.
+ */
+ public static FastFutureStore getInstance(int cap) {
+ if (null == singletonInstance) {
+ synchronized(FastFutureStore.class) {
+ if (null == singletonInstance) {
+ singletonInstance = new FastFutureStore(cap);
+ }
+ }
+ }
+
+ return singletonInstance;
+ }
+
+ /**
+ * Add an object.
+ *
+ * @return Identifier of the added object
+ */
+ public long put(RpcFuture fut) {
+ int loopCount = 0;
+
+ // loop until finding an empty slot
+ while (true) {
+ long currentCounter = slotCounter.getAndIncrement();
+
+ if (currentCounter >= COUNTER_VALUE_BOUNDARY) {
+ slotCounter.getAndSet(0);
+ continue;
+ }
+
+ int slot = mapSlot(currentCounter);
+
+ // try to put object into current slot
+ boolean success = futArray.compareAndSet(slot, null, fut);
+ if (success) {
+ fut.setCorrelationId(currentCounter);
+ return currentCounter;
+ }
+
+ // loopCount is bigger than capacity indicating FastFutureStore has ran out of space
+ if (++loopCount >= cap) {
+ LOG.debug("FutureStore exhausted, current=%d, store id in map", cap);
+ return storeInMap(currentCounter, fut);
+ }
+ }
+ }
+
+ /**
+ * Retrieve object identified by id
+ *
+ * @param id Identifier returned by #{@link #put(RpcFuture)}
+ */
+ public RpcFuture get(long id) {
+ if (isStoredInMap(id)) {
+ return getFromMap(id);
+ }
+
+ return getFromArray(id);
+ }
+
+ /**
+ * Retrieve and remove object identified by id
+ *
+ * @param id Identifier returned by #{@link #put(RpcFuture)}
+ *
+ * @return null if nothing found
+ */
+ public RpcFuture getAndRemove(long id) {
+ if (isStoredInMap(id)) {
+ return getAndRemoveMap(id);
+ }
+
+ return getAndRemoveArray(id);
+ }
+
+ /**
+ * Return the count of the objects.
+ */
+ public int size() {
+ int sum = 0;
+ for (int ix = 0; ix < cap; ++ix) {
+ if (null != futArray.get(ix)) {
+ ++sum;
+ }
+ }
+
+ return sum + backupMap.size();
+ }
+
+ /**
+ * Traverse and invoke #{@link StoreWalker} on every element.
+ *
+ * @param walker Define the action needed to be performed for elements
+ */
+ public void traverse(StoreWalker walker) {
+ if (null == walker) {
+ throw new NullPointerException("walker cannot be null");
+ }
+
+ // do not traverse if no elements inside
+ if (!hasElements()) {
+ return;
+ }
+
+ // traverse array
+ for (int ix = 0; ix < cap; ++ix) {
+ RpcFuture fut = futArray.get(ix);
+ if (null == fut) {
+ // skip empty slot
+ continue;
+ }
+
+ boolean keep = walker.visitElement(fut);
+ if (!keep) {
+ // delete element
+ futArray.set(ix, null);
+ // invoke hook action
+ walker.actionAfterDelete(fut);
+ }
+ }
+
+ // traverse map
+ for (Map.Entry pair : backupMap.entrySet()) {
+ boolean keep = walker.visitElement(pair.getValue());
+ if (!keep) {
+ backupMap.remove(pair.getKey());
+ walker.actionAfterDelete(pair.getValue());
+ }
+ }
+ }
+
+ private RpcFuture getAndRemoveMap(long id) {
+ return backupMap.remove(id);
+ }
+
+ private RpcFuture getAndRemoveArray(long id) {
+ int slot = mapSlot(id);
+ if (!rangeCheck(slot)) {
+ return null;
+ }
+
+ // get the old value
+ RpcFuture prev = futArray.get(slot);
+ // remove only when RpcFuture.logId is equal with current id
+ if (null != prev && prev.getCorrelationId() == id) {
+ futArray.set(slot, null);
+ return prev;
+ }
+
+ return null;
+
+ }
+
+ private RpcFuture getFromMap(long id) {
+ return backupMap.get(id);
+ }
+
+ private RpcFuture getFromArray(long id) {
+ int slot = mapSlot(id);
+ if (!rangeCheck(slot)) {
+ return null;
+ }
+
+ return futArray.get(slot);
+ }
+
+ private long storeInMap(long id, RpcFuture fut) {
+ long mapId = markMapBit(id);
+ backupMap.put(mapId, fut);
+
+ return mapId;
+ }
+
+ private long markMapBit(long id) {
+ return id | COUNTER_VALUE_BOUNDARY;
+ }
+
+ private boolean isStoredInMap(long id) {
+ return id >= COUNTER_VALUE_BOUNDARY;
+ }
+
+ private boolean hasElements() {
+ boolean arrayNotEmpty = false;
+ for (int ix = 0; ix < cap; ++ix) {
+ if (null != futArray.get(ix)) {
+ arrayNotEmpty = true;
+ break;
+ }
+ }
+
+ boolean mapNotEmpty = !backupMap.isEmpty();
+
+ return arrayNotEmpty || mapNotEmpty;
+ }
+
+ private int mapSlot(long id) {
+ return (int) (id % cap);
+ }
+
+ private boolean rangeCheck(int index) {
+ return index < cap && index >= 0;
+ }
+
+ public interface StoreWalker {
+ /**
+ * Action on a none-null element.
+ *
+ * @param fut The current element, may not be null
+ *
+ * @return Indicate whether this element should be deleted. False: delete, True: reserve
+ */
+ boolean visitElement(RpcFuture fut);
+
+ /**
+ * Action performed after a deletion of the element.
+ *
+ * @param fut The deleted object.
+ */
+ void actionAfterDelete(RpcFuture fut);
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/MethodUtils.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/MethodUtils.java
new file mode 100644
index 00000000..b37b83ee
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/MethodUtils.java
@@ -0,0 +1,65 @@
+package com.baidu.brpc.client;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Future;
+
+import com.baidu.brpc.JprotobufRpcMethodInfo;
+import com.baidu.brpc.ProtobufRpcMethodInfo;
+import com.baidu.brpc.RpcMethodInfo;
+import com.baidu.brpc.utils.ProtobufUtils;
+
+public class MethodUtils {
+
+ public static RpcMethodInfo getRpcMethodInfo(Class clazz, String methodName) {
+ Method[] methods = clazz.getMethods();
+ for (Method method : methods) {
+ if (!method.getName().equals(methodName)) {
+ continue;
+ }
+ Class[] parameterTypes = method.getParameterTypes();
+ int paramLength = parameterTypes.length;
+ if (paramLength < 1) {
+ throw new IllegalArgumentException(
+ "invalid params, the correct is ([RpcContext], Request, [Callback])");
+ }
+ if (Future.class.isAssignableFrom(method.getReturnType())
+ && (paramLength < 1 || !RpcCallback.class.isAssignableFrom(parameterTypes[paramLength - 1]))) {
+ throw new IllegalArgumentException("returnType is Future, but last argument is not RpcCallback");
+ }
+
+ Method syncMethod = method;
+ if (paramLength > 1) {
+ int startIndex = 0;
+ int endIndex = paramLength - 1;
+ // has callback, async rpc
+ if (RpcCallback.class.isAssignableFrom(parameterTypes[paramLength - 1])) {
+ endIndex--;
+ paramLength--;
+ }
+ Class[] actualParameterTypes = new Class[paramLength];
+ for (int i = 0; startIndex <= endIndex; i++) {
+ actualParameterTypes[i] = parameterTypes[startIndex++];
+ }
+ try {
+ syncMethod = method.getDeclaringClass().getMethod(
+ method.getName(), actualParameterTypes);
+ } catch (NoSuchMethodException ex) {
+ throw new IllegalArgumentException("can not find sync method:" + method.getName());
+ }
+ }
+
+ RpcMethodInfo methodInfo;
+ ProtobufUtils.MessageType messageType = ProtobufUtils.getMessageType(syncMethod);
+ if (messageType == ProtobufUtils.MessageType.PROTOBUF) {
+ methodInfo = new ProtobufRpcMethodInfo(syncMethod);
+ } else if (messageType == ProtobufUtils.MessageType.JPROTOBUF) {
+ methodInfo = new JprotobufRpcMethodInfo(syncMethod);
+ } else {
+ methodInfo = new RpcMethodInfo(syncMethod);
+ }
+
+ return methodInfo;
+ }
+ return null;
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcCallback.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcCallback.java
new file mode 100644
index 00000000..2b73a97c
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcCallback.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client;
+
+public interface RpcCallback {
+
+ void success(T response);
+
+ void fail(Throwable e);
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcClient.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcClient.java
new file mode 100644
index 00000000..a4592d4a
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcClient.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (c) 2019 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client;
+
+import com.baidu.brpc.ChannelInfo;
+import com.baidu.brpc.client.channel.BrpcChannel;
+import com.baidu.brpc.client.channel.ChannelType;
+import com.baidu.brpc.client.handler.IdleChannelHandler;
+import com.baidu.brpc.client.handler.RpcClientHandler;
+import com.baidu.brpc.client.instance.BasicInstanceProcessor;
+import com.baidu.brpc.client.instance.Endpoint;
+import com.baidu.brpc.client.instance.EnhancedInstanceProcessor;
+import com.baidu.brpc.client.instance.InstanceProcessor;
+import com.baidu.brpc.client.instance.ServiceInstance;
+import com.baidu.brpc.client.loadbalance.LoadBalanceManager;
+import com.baidu.brpc.client.loadbalance.LoadBalanceStrategy;
+import com.baidu.brpc.client.loadbalance.RandomStrategy;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.interceptor.ClientTraceInterceptor;
+import com.baidu.brpc.interceptor.Interceptor;
+import com.baidu.brpc.interceptor.LoadBalanceInterceptor;
+import com.baidu.brpc.naming.BrpcURL;
+import com.baidu.brpc.naming.ListNamingService;
+import com.baidu.brpc.naming.NamingOptions;
+import com.baidu.brpc.naming.NamingService;
+import com.baidu.brpc.naming.NamingServiceFactory;
+import com.baidu.brpc.naming.NamingServiceFactoryManager;
+import com.baidu.brpc.naming.NotifyListener;
+import com.baidu.brpc.naming.SubscribeInfo;
+import com.baidu.brpc.protocol.Protocol;
+import com.baidu.brpc.protocol.ProtocolManager;
+import com.baidu.brpc.protocol.Request;
+import com.baidu.brpc.server.ServiceManager;
+import com.baidu.brpc.spi.ExtensionLoaderManager;
+import com.baidu.brpc.thread.BrpcIoThreadPoolInstance;
+import com.baidu.brpc.thread.BrpcWorkClientThreadPoolInstance;
+import com.baidu.brpc.thread.ClientCallBackThreadPoolInstance;
+import com.baidu.brpc.thread.ClientTimeoutTimerInstance;
+import com.baidu.brpc.thread.ShutDownManager;
+import com.baidu.brpc.utils.BrpcConstants;
+import com.baidu.brpc.utils.CustomThreadFactory;
+import com.baidu.brpc.utils.ThreadPool;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.epoll.EpollChannelOption;
+import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.epoll.EpollMode;
+import io.netty.channel.epoll.EpollSocketChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.timeout.IdleStateHandler;
+import io.netty.util.Timeout;
+import io.netty.util.Timer;
+import lombok.Getter;
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Created by huwenwei on 2017/4/25.
+ */
+@SuppressWarnings("unchecked")
+@Getter
+public class RpcClient {
+ private static final Logger LOG = LoggerFactory.getLogger(RpcClient.class);
+
+ private RpcClientOptions rpcClientOptions = new RpcClientOptions();
+ private Bootstrap bootstrap;
+ private Timer timeoutTimer;
+ private Protocol protocol;
+ private LoadBalanceStrategy loadBalanceStrategy;
+ private List interceptors = new ArrayList();
+ private LoadBalanceInterceptor loadBalanceInterceptor = new LoadBalanceInterceptor();
+ private NamingService namingService;
+ private ThreadPool workThreadPool;
+ private EventLoopGroup ioThreadPool;
+ private Class serviceInterface;
+ private SubscribeInfo subscribeInfo;
+ private AtomicBoolean stop = new AtomicBoolean(false);
+ private InstanceProcessor instanceProcessor;
+
+ /**
+ * callBack thread when method invoke fail
+ */
+ private ExecutorService callbackThread;
+
+ /**
+ * 保存单例的引用
+ */
+ private FastFutureStore fastFutureStore;
+
+ public RpcClient(String namingServiceUrl) {
+ this(namingServiceUrl, new RpcClientOptions(), null);
+ }
+
+ public RpcClient(String namingServiceUrl, RpcClientOptions options) {
+ this(namingServiceUrl, options, null);
+ }
+
+ /**
+ * parse naming service url, connect to servers
+ *
+ * @param serviceUrl format like "list://127.0.0.1:8200"
+ * @param options rpc client options
+ */
+ public RpcClient(String serviceUrl,
+ final RpcClientOptions options,
+ List interceptors) {
+ Validate.notEmpty(serviceUrl);
+ Validate.notNull(options);
+
+ ExtensionLoaderManager.getInstance().loadAllExtensions(options.getEncoding());
+ // parse naming
+ BrpcURL url = new BrpcURL(serviceUrl);
+ NamingServiceFactory namingServiceFactory
+ = NamingServiceFactoryManager.getInstance().getNamingServiceFactory(url.getSchema());
+ this.namingService = namingServiceFactory.createNamingService(url);
+ boolean singleServer = false;
+ if (namingService instanceof ListNamingService) {
+ List instances = namingService.lookup(null);
+ singleServer = instances.size() == 1;
+ }
+
+ this.init(options, interceptors, singleServer);
+ }
+
+ public RpcClient(Endpoint endPoint) {
+ this(endPoint, null);
+ }
+
+ public RpcClient(Endpoint endPoint, RpcClientOptions options) {
+ this(endPoint, options, null);
+ }
+
+ public RpcClient(Endpoint endPoint, RpcClientOptions options, List interceptors) {
+ if (null == options) {
+ options = new RpcClientOptions();
+ }
+ ExtensionLoaderManager.getInstance().loadAllExtensions(options.getEncoding());
+ this.init(options, interceptors, true);
+ instanceProcessor.addInstance(new ServiceInstance(endPoint));
+ }
+
+ public RpcClient(List endPoints) {
+ this(endPoints, new RpcClientOptions(), null);
+ }
+
+ public RpcClient(List endPoints, RpcClientOptions options, List interceptors) {
+ ExtensionLoaderManager.getInstance().loadAllExtensions(options.getEncoding());
+ this.init(options, interceptors, endPoints.size() == 1);
+ for (Endpoint endpoint : endPoints) {
+ instanceProcessor.addInstance(new ServiceInstance(endpoint));
+ }
+ }
+
+ public static T getProxy(RpcClient rpcClient, Class clazz, NamingOptions namingOptions) {
+ return BrpcProxy.getProxy(rpcClient, clazz, namingOptions);
+ }
+
+ public static T getProxy(RpcClient rpcClient, Class clazz) {
+ return BrpcProxy.getProxy(rpcClient, clazz, null);
+ }
+
+ /**
+ * @param service
+ */
+ public void registerPushService(Object service) {
+ ServiceManager.getInstance().registerPushService(service);
+
+ // 如果只注册了pushService,没有注册一个普通的服务的话, 报错
+ if (instanceProcessor.getInstances().size() == 0) {
+ LOG.error("there should be have normal services before register push service.");
+ throw new RpcException("there should be have normal services before register push service");
+ }
+ }
+
+ public T getProxy(Class clazz, NamingOptions namingOptions) {
+ return BrpcProxy.getProxy(this, clazz, namingOptions);
+ }
+
+ public T getProxy(Class clazz) {
+ return BrpcProxy.getProxy(this, clazz, null);
+ }
+
+ public void setServiceInterface(Class clazz) {
+ setServiceInterface(clazz, null);
+ }
+
+ public void setServiceInterface(Class clazz, NamingOptions namingOptions) {
+ if (this.serviceInterface != null) {
+ throw new RpcException("serviceInterface must not be set repeatedly, please use another RpcClient");
+ }
+ if (clazz.getInterfaces().length == 0) {
+ this.serviceInterface = clazz;
+ } else {
+ // if it is async interface, we should subscribe the sync interface
+ this.serviceInterface = clazz.getInterfaces()[0];
+ }
+
+ if (namingService != null) {
+ if (namingOptions != null) {
+ subscribeInfo = new SubscribeInfo(namingOptions);
+ } else {
+ subscribeInfo = new SubscribeInfo();
+ }
+ subscribeInfo.setInterfaceName(serviceInterface.getName());
+ List instances = this.namingService.lookup(subscribeInfo);
+ instanceProcessor.addInstances(instances);
+ this.namingService.subscribe(subscribeInfo, new NotifyListener() {
+ @Override
+ public void notify(Collection addList,
+ Collection deleteList) {
+ instanceProcessor.addInstances(addList);
+ instanceProcessor.deleteInstances(deleteList);
+ }
+ });
+ }
+ }
+
+ public void shutdown() {
+ stop();
+ }
+
+ public void stop() {
+ // avoid stop multi times
+ if (stop.compareAndSet(false, true)) {
+ if (namingService != null) {
+ namingService.unsubscribe(subscribeInfo);
+ }
+ if (instanceProcessor != null) {
+ instanceProcessor.stop();
+ }
+ if (loadBalanceStrategy != null) {
+ loadBalanceStrategy.destroy();
+ }
+ if (ioThreadPool != null && !rpcClientOptions.isGlobalThreadPoolSharing()) {
+ ioThreadPool.shutdownGracefully().syncUninterruptibly();
+ }
+ if (workThreadPool != null && !rpcClientOptions.isGlobalThreadPoolSharing()) {
+ workThreadPool.stop();
+ }
+ }
+ }
+
+ public boolean isShutdown() {
+ return stop.get();
+ }
+
+ /**
+ * select instance by load balance and select channel from the instance.
+ * when user call this function explicitly, he should return the channel to avoid connection leak.
+ *
+ * @return netty channel
+ */
+ public Channel selectChannel(Request request) {
+ BrpcChannel brpcChannel = loadBalanceStrategy.selectInstance(
+ request,
+ instanceProcessor.getHealthyInstanceChannels(),
+ request.getSelectedInstances());
+ if (brpcChannel == null) {
+ LOG.debug("no available healthy server, so random select one unhealthy server");
+ RandomStrategy randomStrategy = new RandomStrategy();
+ randomStrategy.init(this);
+ brpcChannel = randomStrategy.selectInstance(
+ request,
+ instanceProcessor.getUnHealthyInstanceChannels(),
+ request.getSelectedInstances());
+ if (brpcChannel == null) {
+ throw new RpcException(RpcException.NETWORK_EXCEPTION, "no available instance");
+ }
+ }
+ Channel channel;
+ try {
+ channel = brpcChannel.getChannel();
+ } catch (NoSuchElementException full) {
+ int maxConnections = brpcChannel.getCurrentMaxConnection() * 2;
+ brpcChannel.updateMaxConnection(maxConnections);
+ String errMsg = String.format("channel pool is exhausted, and double maxTotalConnection,server=%s:%d",
+ brpcChannel.getServiceInstance().getIp(), brpcChannel.getServiceInstance().getPort());
+ LOG.debug(errMsg);
+ throw new RpcException(RpcException.NETWORK_EXCEPTION, errMsg, full);
+ } catch (IllegalStateException illegalState) {
+ String errMsg = String.format("channel pool is closed, server=%s:%d",
+ brpcChannel.getServiceInstance().getIp(), brpcChannel.getServiceInstance().getPort());
+ LOG.debug(errMsg);
+ throw new RpcException(RpcException.UNKNOWN_EXCEPTION, errMsg, illegalState);
+ } catch (Exception connectedFailed) {
+ String errMsg;
+ if (rpcClientOptions.getChannelType() == ChannelType.POOLED_CONNECTION) {
+ errMsg = String.format("channel pool make new object failed, "
+ + "active=%d,idle=%d,server=%s:%d, ex=%s",
+ brpcChannel.getActiveConnectionNum(),
+ brpcChannel.getIdleConnectionNum(),
+ brpcChannel.getServiceInstance().getIp(),
+ brpcChannel.getServiceInstance().getPort(),
+ connectedFailed.getMessage());
+ } else {
+ errMsg = String.format("get channel failed, ex=%s", connectedFailed.getMessage());
+ }
+ LOG.debug(errMsg);
+ throw new RpcException(RpcException.UNKNOWN_EXCEPTION, errMsg, connectedFailed);
+ }
+
+ if (channel == null) {
+ String errMsg = "channel is null, retry another channel";
+ LOG.debug(errMsg);
+ throw new RpcException(RpcException.UNKNOWN_EXCEPTION, errMsg);
+ }
+ if (!channel.isActive()) {
+ brpcChannel.incFailedNum();
+ // 如果连接不是有效的,从连接池中剔除。
+ brpcChannel.removeChannel(channel);
+ String errMsg = "channel is non active, retry another channel";
+ throw new RpcException(RpcException.NETWORK_EXCEPTION, errMsg);
+ }
+ return channel;
+ }
+
+ /**
+ * select channel from endpoint which is selected by custom load balance.
+ *
+ * @param endpoint ip:port
+ * @return netty channel
+ */
+ public Channel selectChannel(Endpoint endpoint) {
+ BrpcChannel brpcChannel = instanceProcessor.getInstanceChannelMap().get(endpoint);
+ if (brpcChannel == null) {
+ LOG.warn("instance:{} not found, may be it is removed from naming service.", endpoint);
+ throw new RpcException(RpcException.SERVICE_EXCEPTION, "instance not found:" + endpoint);
+ }
+ Channel channel;
+ try {
+ channel = brpcChannel.getChannel();
+ } catch (Exception ex) {
+ throw new RpcException(RpcException.NETWORK_EXCEPTION, "select channel failed from " + endpoint, ex);
+ }
+ if (!channel.isActive()) {
+ brpcChannel.incFailedNum();
+ // 如果连接不是有效的,从连接池中剔除。
+ brpcChannel.removeChannel(channel);
+ String errMsg = "channel is non active, retry another channel";
+ throw new RpcException(RpcException.NETWORK_EXCEPTION, errMsg);
+ }
+ return channel;
+ }
+
+ public void returnChannel(Channel channel) {
+ ChannelInfo channelInfo = ChannelInfo.getClientChannelInfo(channel);
+ channelInfo.getChannelGroup().returnChannel(channel);
+ }
+
+ public void removeChannel(Channel channel) {
+ ChannelInfo channelInfo = ChannelInfo.getClientChannelInfo(channel);
+ channelInfo.getChannelGroup().removeChannel(channel);
+ }
+
+ public AsyncAwareFuture sendRequest(Request request) {
+ // select instance by load balance, and select channel from instance.
+ Channel channel = selectChannel(request);
+ request.setChannel(channel);
+ ChannelInfo channelInfo = ChannelInfo.getClientChannelInfo(request.getChannel());
+ BrpcChannel brpcChannel = channelInfo.getChannelGroup();
+ protocol.beforeRequestSent(request, this, brpcChannel);
+
+ // encode request
+ RpcFuture rpcFuture = RpcFuture.createRpcFuture(request, this);
+ channelInfo.setCorrelationId(rpcFuture.getCorrelationId());
+ rpcFuture.setChannelInfo(channelInfo);
+ request.setRpcFuture(rpcFuture);
+ request.setCorrelationId(rpcFuture.getCorrelationId());
+ try {
+ request.setSendBuf(protocol.encodeRequest(request));
+ } catch (Throwable t) {
+ throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, t.getMessage(), t);
+ }
+
+ // register timeout timer
+ RpcTimeoutTimer timeoutTask = new RpcTimeoutTimer(channelInfo, request.getCorrelationId(), this);
+ Timeout timeout = timeoutTimer.newTimeout(timeoutTask, request.getReadTimeoutMillis(), TimeUnit.MILLISECONDS);
+ request.getRpcFuture().setTimeout(timeout);
+ try {
+ // netty will release the send buffer after sent.
+ // we retain here, so it can be used when rpc retry.
+ request.retain();
+ ChannelFuture sendFuture = request.getChannel().writeAndFlush(request.getSendBuf());
+ sendFuture.awaitUninterruptibly(request.getWriteTimeoutMillis());
+ if (!sendFuture.isSuccess()) {
+ if (!(sendFuture.cause() instanceof ClosedChannelException)) {
+ LOG.warn("send request failed, channelActive={}, ex=",
+ request.getChannel().isActive(), sendFuture.cause());
+ }
+ String errMsg = String.format("send request failed, channelActive=%b",
+ request.getChannel().isActive());
+ throw new RpcException(RpcException.NETWORK_EXCEPTION, errMsg);
+ }
+ } catch (Exception ex) {
+ channelInfo.handleRequestFail(rpcClientOptions.getChannelType(), request.getCorrelationId());
+ timeout.cancel();
+ if (ex instanceof RpcException) {
+ throw (RpcException) ex;
+ } else {
+ throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, ex.getMessage(), ex);
+ }
+ }
+
+ // return channel
+ channelInfo.handleRequestSuccess(rpcClientOptions.getChannelType());
+ return request.getRpcFuture();
+ }
+
+ public void triggerCallback(Runnable runnable) {
+ if (!callbackThread.isTerminated()) {
+ callbackThread.execute(runnable);
+ }
+ }
+
+ private void init(final RpcClientOptions options, List interceptors, boolean singleServer) {
+ Validate.notNull(options);
+ try {
+ this.rpcClientOptions.copyFrom(options);
+ } catch (Exception ex) {
+ LOG.warn("init rpc options failed, so use default");
+ }
+ if (interceptors != null) {
+ this.interceptors.addAll(interceptors);
+ }
+ this.interceptors.add(new ClientTraceInterceptor());
+ this.protocol = ProtocolManager.getInstance().getProtocol(options.getProtocolType());
+ if (!protocol.returnChannelBeforeResponse()
+ && rpcClientOptions.getChannelType() == ChannelType.SINGLE_CONNECTION) {
+ String errorString = "it can't use SINGLE_CONNECTION when protocol returns channel before response";
+ LOG.error(errorString);
+ throw new IllegalArgumentException(errorString);
+ }
+ fastFutureStore = FastFutureStore.getInstance(options.getFutureBufferSize());
+ timeoutTimer = ClientTimeoutTimerInstance.getOrCreateInstance();
+
+ // singleServer do not need healthChecker
+ if (singleServer) {
+ instanceProcessor = new BasicInstanceProcessor(this);
+ } else {
+ instanceProcessor = new EnhancedInstanceProcessor(this);
+ }
+
+ // 负载均衡算法
+ loadBalanceStrategy = LoadBalanceManager.getInstance().createLoadBalance(
+ rpcClientOptions.getLoadBalanceType());
+ loadBalanceStrategy.init(this);
+
+ // init once
+ ShutDownManager.getInstance();
+ boolean threadPoolSharing = rpcClientOptions.isGlobalThreadPoolSharing();
+ if (threadPoolSharing) {
+ this.workThreadPool =
+ BrpcWorkClientThreadPoolInstance.getOrCreateInstance(rpcClientOptions.getWorkThreadNum());
+ if (rpcClientOptions.getIoEventType() == BrpcConstants.IO_EVENT_NETTY_EPOLL) {
+ ioThreadPool = BrpcIoThreadPoolInstance.getOrCreateEpollInstance(options.getIoThreadNum());
+ } else {
+ ioThreadPool = BrpcIoThreadPoolInstance.getOrCreateNioInstance(options.getIoThreadNum());
+ }
+ } else {
+ this.workThreadPool = new ThreadPool(rpcClientOptions.getWorkThreadNum(),
+ new CustomThreadFactory("client-work-thread"));
+ if (rpcClientOptions.getIoEventType() == BrpcConstants.IO_EVENT_NETTY_EPOLL) {
+ ioThreadPool = new EpollEventLoopGroup(options.getIoThreadNum(),
+ new CustomThreadFactory("client-io-thread"));
+ } else {
+ ioThreadPool = new NioEventLoopGroup(options.getIoThreadNum(),
+ new CustomThreadFactory("client-io-thread"));
+ }
+ }
+
+ this.callbackThread = ClientCallBackThreadPoolInstance.getOrCreateInstance(1);
+
+ // init netty bootstrap
+ bootstrap = new Bootstrap();
+ if (rpcClientOptions.getIoEventType() == BrpcConstants.IO_EVENT_NETTY_EPOLL) {
+ bootstrap.channel(EpollSocketChannel.class);
+ bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED);
+ } else {
+ bootstrap.channel(NioSocketChannel.class);
+ }
+
+ bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, rpcClientOptions.getConnectTimeoutMillis());
+ bootstrap.option(ChannelOption.SO_KEEPALIVE, rpcClientOptions.isKeepAlive());
+ bootstrap.option(ChannelOption.SO_REUSEADDR, rpcClientOptions.isReuseAddr());
+ bootstrap.option(ChannelOption.TCP_NODELAY, rpcClientOptions.isTcpNoDelay());
+ bootstrap.option(ChannelOption.SO_RCVBUF, rpcClientOptions.getReceiveBufferSize());
+ bootstrap.option(ChannelOption.SO_SNDBUF, rpcClientOptions.getSendBufferSize());
+ final RpcClientHandler rpcClientHandler = new RpcClientHandler(RpcClient.this);
+ final ChannelInitializer initializer = new ChannelInitializer() {
+ @Override
+ protected void initChannel(SocketChannel ch) throws Exception {
+ if (rpcClientOptions.getChannelType() == ChannelType.SINGLE_CONNECTION) {
+ ch.pipeline().addLast(new IdleStateHandler(0, 0, rpcClientOptions.getKeepAliveTime()));
+ ch.pipeline().addLast(new IdleChannelHandler());
+ }
+ ch.pipeline().addLast(rpcClientHandler);
+ }
+ };
+
+ bootstrap.group(ioThreadPool).handler(initializer);
+ }
+
+ public void removeLogId(long id) {
+ fastFutureStore.getAndRemove(id);
+ }
+
+ public RpcClientOptions getRpcClientOptions() {
+ return rpcClientOptions;
+ }
+
+ public Protocol getProtocol() {
+ return protocol;
+ }
+
+ public CopyOnWriteArrayList getHealthyInstances() {
+ return instanceProcessor.getHealthyInstanceChannels();
+ }
+
+ public List getInterceptors() {
+ return interceptors;
+ }
+
+ public Bootstrap getBootstrap() {
+ return bootstrap;
+ }
+
+ public ThreadPool getWorkThreadPool() {
+ return workThreadPool;
+ }
+
+ public LoadBalanceStrategy getLoadBalanceStrategy() {
+ return loadBalanceStrategy;
+ }
+
+ public boolean isLongConnection() {
+ return rpcClientOptions.getChannelType() != ChannelType.SHORT_CONNECTION;
+ }
+
+ public NamingService getNamingService() {
+ return namingService;
+ }
+
+ public Timer getTimeoutTimer() {
+ return timeoutTimer;
+ }
+
+ public LoadBalanceInterceptor getLoadBalanceInterceptor() {
+ return loadBalanceInterceptor;
+ }
+
+ public void setLoadBalanceInterceptor(LoadBalanceInterceptor loadBalanceInterceptor) {
+ this.loadBalanceInterceptor = loadBalanceInterceptor;
+ }
+
+ public SubscribeInfo getSubscribeInfo() {
+ return subscribeInfo;
+ }
+
+ public InstanceProcessor getInstanceProcessor() {
+ return instanceProcessor;
+ }
+
+ public void setInstanceProcessor(InstanceProcessor instanceProcessor) {
+ this.instanceProcessor = instanceProcessor;
+ }
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcClientOptions.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcClientOptions.java
new file mode 100644
index 00000000..58774f18
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcClientOptions.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client;
+
+import com.baidu.brpc.client.channel.ChannelType;
+import com.baidu.brpc.client.loadbalance.LoadBalanceStrategy;
+import com.baidu.brpc.protocol.Options;
+import com.baidu.brpc.utils.BrpcConstants;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Created by wenweihu86 on 2017/4/24.
+ */
+@Setter
+@Getter
+@NoArgsConstructor
+public class RpcClientOptions {
+
+ private int protocolType = Options.ProtocolType.PROTOCOL_BAIDU_STD_VALUE;
+ private int connectTimeoutMillis = 1000;
+ private int readTimeoutMillis = 1000;
+ private int writeTimeoutMillis = 1000;
+ private int maxTotalConnections = 8;
+ private int minIdleConnections = 8;
+ private int maxTryTimes = 3;
+ // Maximum time for connection idle, testWhileIdle needs to be true
+ private long timeBetweenEvictionRunsMillis = 5 * 60 * 1000;
+ private int loadBalanceType = LoadBalanceStrategy.LOAD_BALANCE_FAIR;
+ // for fair load balance strategy only
+ private int latencyWindowSizeOfFairLoadBalance = 30;
+ // for fair load balance strategy only
+ // the ratio of activeInstancesNum/totalInstancesNum in brpc client, if this ratio not reached,
+ // fair load balance will not start, just use random load balance strategy
+ private float activeInstancesRatioOfFairLoadBalance = 0.5f;
+ private int healthyCheckIntervalMillis = 3000;
+ // The keep alive
+ private boolean keepAlive = true;
+ private boolean reuseAddr = true;
+ private boolean tcpNoDelay = true;
+ // so linger
+ private int soLinger = 5;
+ // backlog
+ private int backlog = 100;
+ // receive buffer size
+ private int receiveBufferSize = 1024 * 64;
+ // send buffer size
+ private int sendBufferSize = 1024 * 64;
+ // keep alive time in seconds
+ private int keepAliveTime = 60 * 5;
+ // io threads, default use Netty default value
+ private int ioThreadNum = Runtime.getRuntime().availableProcessors();
+ // threads used for deserialize rpc response and execute the callback
+ private int workThreadNum = Runtime.getRuntime().availableProcessors();
+ /**
+ * io event type, netty or jdk
+ */
+ private int ioEventType = BrpcConstants.IO_EVENT_JDK;
+ // FastFutureStore's max size
+ private int futureBufferSize = 1000000;
+ private String encoding = "utf-8";
+ private Options.CompressType compressType = Options.CompressType.COMPRESS_TYPE_NONE;
+ private ChannelType channelType = ChannelType.POOLED_CONNECTION;
+ private String clientName;
+
+ // share worker thread poll and event thread pool between multi RpcClients
+ private boolean globalThreadPoolSharing = false;
+
+ public RpcClientOptions(RpcClientOptions options) {
+ this.copyFrom(options);
+ }
+
+ public void copyFrom(RpcClientOptions another) {
+ this.activeInstancesRatioOfFairLoadBalance = another.activeInstancesRatioOfFairLoadBalance;
+ this.backlog = another.backlog;
+ this.channelType = another.channelType;
+ this.compressType = another.compressType;
+ this.connectTimeoutMillis = another.connectTimeoutMillis;
+ this.encoding = another.encoding;
+ this.futureBufferSize = another.futureBufferSize;
+ this.healthyCheckIntervalMillis = another.healthyCheckIntervalMillis;
+ this.ioThreadNum = another.ioThreadNum;
+ this.keepAlive = another.keepAlive;
+ this.keepAliveTime = another.keepAliveTime;
+ this.latencyWindowSizeOfFairLoadBalance = another.latencyWindowSizeOfFairLoadBalance;
+ this.loadBalanceType = another.loadBalanceType;
+ this.maxTotalConnections = another.maxTotalConnections;
+ this.maxTryTimes = another.maxTryTimes;
+ this.minIdleConnections = another.minIdleConnections;
+ this.protocolType = another.protocolType;
+ this.readTimeoutMillis = another.readTimeoutMillis;
+ this.receiveBufferSize = another.receiveBufferSize;
+ this.reuseAddr = another.reuseAddr;
+ this.sendBufferSize = another.sendBufferSize;
+ this.soLinger = another.soLinger;
+ this.tcpNoDelay = another.tcpNoDelay;
+ this.timeBetweenEvictionRunsMillis = another.timeBetweenEvictionRunsMillis;
+ this.workThreadNum = another.workThreadNum;
+ this.writeTimeoutMillis = another.writeTimeoutMillis;
+ this.clientName = another.clientName;
+ this.globalThreadPoolSharing = another.globalThreadPoolSharing;
+ }
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcFuture.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcFuture.java
new file mode 100644
index 00000000..cf7fb945
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcFuture.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 Baidu, Inc. All Rights Reserved.
+ */
+
+package com.baidu.brpc.client;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import com.baidu.brpc.protocol.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.baidu.brpc.ChannelInfo;
+import com.baidu.brpc.RpcContext;
+import com.baidu.brpc.RpcMethodInfo;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.protocol.Response;
+import com.baidu.brpc.utils.CollectionUtils;
+
+import io.netty.util.Timeout;
+import lombok.Getter;
+import lombok.Setter;
+
+@SuppressWarnings("unchecked")
+@Setter
+@Getter
+public class RpcFuture implements AsyncAwareFuture {
+ private static final Logger LOG = LoggerFactory.getLogger(RpcFuture.class);
+
+ protected CountDownLatch latch;
+ protected Timeout timeout;
+
+ protected RpcCallback callback; // callback cannot be set after init
+ protected ChannelInfo channelInfo;
+ protected RpcClient rpcClient;
+ protected RpcMethodInfo rpcMethodInfo;
+
+ protected Response response;
+ protected boolean isDone;
+ // record the time of request
+ // used in FAIR load balancing
+ protected long startTime;
+ protected long endTime;
+
+ protected volatile long correlationId;
+
+ public RpcFuture() {
+ this.latch = new CountDownLatch(1);
+ this.startTime = System.currentTimeMillis();
+ }
+
+ public RpcFuture(long correlationId) {
+ this.correlationId = correlationId;
+ this.latch = new CountDownLatch(1);
+ this.startTime = System.currentTimeMillis();
+ }
+
+ public RpcFuture(Timeout timeout,
+ RpcMethodInfo rpcMethodInfo,
+ RpcCallback callback,
+ ChannelInfo channelInfo,
+ RpcClient rpcClient) {
+ init(timeout, rpcMethodInfo, callback, channelInfo, rpcClient);
+ }
+
+ public static RpcFuture createRpcFuture(Request request, RpcClient rpcClient) {
+ // create RpcFuture object
+ RpcFuture rpcFuture = new RpcFuture();
+ rpcFuture.setRpcMethodInfo(request.getRpcMethodInfo());
+ rpcFuture.setCallback(request.getCallback());
+ rpcFuture.setRpcClient(rpcClient);
+ // generate correlationId
+ FastFutureStore.getInstance(0).put(rpcFuture);
+ return rpcFuture;
+ }
+
+ public void init(Timeout timeout,
+ RpcMethodInfo rpcMethodInfo,
+ RpcCallback callback,
+ ChannelInfo channelInfo,
+ RpcClient rpcClient) {
+ this.timeout = timeout;
+ this.rpcMethodInfo = rpcMethodInfo;
+ this.callback = callback;
+ this.channelInfo = channelInfo;
+ this.latch = new CountDownLatch(1);
+ this.startTime = System.currentTimeMillis();
+ this.rpcClient = rpcClient;
+ }
+
+ public void handleConnection(Response response) {
+ this.response = response;
+ this.endTime = System.currentTimeMillis();
+
+ // only long connection need to update channel group
+ if (rpcClient.isLongConnection()) {
+ if (response != null && response.getResult() != null) {
+ channelInfo.getChannelGroup().updateLatency((int) (endTime - startTime));
+ channelInfo.handleResponseSuccess();
+ } else {
+ channelInfo.getChannelGroup().updateLatencyWithReadTimeOut();
+ channelInfo.handleResponseFail();
+ }
+ } else {
+ channelInfo.close();
+ }
+
+ timeout.cancel();
+ latch.countDown();
+ isDone = true;
+ }
+
+ public void handleResponse(Response response) {
+ handleConnection(response);
+ // invoke the chain of interceptors when async scene
+ if (isAsync() && CollectionUtils.isNotEmpty(rpcClient.getInterceptors())) {
+ int length = rpcClient.getInterceptors().size();
+ for (int i = length - 1; i >= 0; i--) {
+ rpcClient.getInterceptors().get(i).handleResponse(response);
+ }
+ }
+
+ if (isAsync()) {
+ setRpcContext();
+ if (response == null) {
+ callback.fail(new RpcException(RpcException.SERVICE_EXCEPTION, "internal error"));
+ } else if (response.getResult() != null) {
+ callback.success((T) response.getResult());
+ } else {
+ callback.fail(response.getException());
+ }
+ }
+ }
+
+ @Override
+ public boolean isAsync() {
+ return callback != null;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return isDone;
+ }
+
+ @Override
+ public T get() throws InterruptedException {
+ latch.await();
+ if (response != null && response.getException() != null) {
+ throw new RpcException(response.getException());
+ }
+ if (response == null) {
+ throw new RpcException(RpcException.TIMEOUT_EXCEPTION);
+ }
+ setRpcContext();
+ return (T) response.getResult();
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit) {
+ try {
+ boolean ret = latch.await(timeout, unit);
+ if (!ret) {
+ throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "timeout");
+ }
+ assert response != null;
+ if (response.getException() != null) {
+ throw new RpcException(response.getException());
+ }
+ setRpcContext();
+ return (T) response.getResult();
+ } catch (InterruptedException e) {
+ throw new RpcException(RpcException.UNKNOWN_EXCEPTION, e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "@correlationId = " + correlationId;
+ }
+
+ protected void setRpcContext() {
+ if (response == null) {
+ return;
+ }
+ if (response.getBinaryAttachment() != null
+ || response.getKvAttachment() != null) {
+ RpcContext rpcContext = RpcContext.getContext();
+ if (response.getBinaryAttachment() != null) {
+ rpcContext.setResponseBinaryAttachment(response.getBinaryAttachment());
+ }
+ if (response.getKvAttachment() != null) {
+ rpcContext.setResponseKvAttachment(response.getKvAttachment());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcTimeoutTimer.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcTimeoutTimer.java
new file mode 100644
index 00000000..b47497bb
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/RpcTimeoutTimer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.brpc.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.baidu.brpc.ChannelInfo;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.protocol.Response;
+import com.baidu.brpc.server.RpcServer;
+
+import io.netty.util.Timeout;
+import io.netty.util.TimerTask;
+
+/**
+ * Created by wanghongfei on 2019-04-18.
+ */
+public class RpcTimeoutTimer implements TimerTask {
+ private static final Logger LOG = LoggerFactory.getLogger(RpcTimeoutTimer.class);
+
+ private ChannelInfo channelInfo;
+ private long correlationId;
+ private RpcClient rpcClient;
+ private RpcServer rpcServer;
+
+ public RpcTimeoutTimer(
+ ChannelInfo channelInfo,
+ long correlationId,
+ RpcClient rpcClient) {
+ this.channelInfo = channelInfo;
+ this.correlationId = correlationId;
+ this.rpcClient = rpcClient;
+ }
+
+ public RpcTimeoutTimer(
+ ChannelInfo channelInfo,
+ long correlationId,
+ RpcServer rpcServer) {
+ this.channelInfo = channelInfo;
+ this.correlationId = correlationId;
+ this.rpcServer = rpcServer;
+ }
+
+ @Override
+ public void run(Timeout timeout) {
+ RpcFuture future = channelInfo.removeRpcFuture(correlationId);
+ if (future != null) {
+ String ip = future.getChannelInfo().getChannelGroup().getServiceInstance().getIp();
+ int port = future.getChannelInfo().getChannelGroup().getServiceInstance().getPort();
+ long elapseTime = System.currentTimeMillis() - future.getStartTime();
+ String errMsg = String.format("request timeout,correlationId=%d,ip=%s,port=%d,elapse=%dms",
+ correlationId, ip, port, elapseTime);
+ LOG.info(errMsg);
+ Response response = rpcClient != null ? rpcClient.getProtocol().createResponse() :
+ rpcServer.getProtocol().createResponse();
+ response.setException(new RpcException(RpcException.TIMEOUT_EXCEPTION, errMsg));
+ response.setRpcFuture(future);
+ future.handleResponse(response);
+ }
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/AbstractBrpcChannel.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/AbstractBrpcChannel.java
new file mode 100644
index 00000000..13b5ff36
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/AbstractBrpcChannel.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.brpc.client.channel;
+
+import java.net.InetSocketAddress;
+import java.util.Queue;
+
+import com.baidu.brpc.server.push.RegisterService;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+import com.baidu.brpc.RpcMethodInfo;
+import com.baidu.brpc.client.FastFutureStore;
+import com.baidu.brpc.client.MethodUtils;
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.client.RpcClientOptions;
+import com.baidu.brpc.client.RpcFuture;
+import com.baidu.brpc.client.instance.ServiceInstance;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.protocol.Protocol;
+import com.baidu.brpc.protocol.RpcRequest;
+import com.baidu.brpc.protocol.push.SPHead;
+import com.baidu.brpc.protocol.push.ServerPushProtocol;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class AbstractBrpcChannel implements BrpcChannel {
+ protected ServiceInstance serviceInstance;
+ protected Bootstrap bootstrap;
+ protected Protocol protocol;
+ protected RpcClient rpcClient;
+
+ public AbstractBrpcChannel(ServiceInstance serviceInstance, Bootstrap bootstrap, Protocol protocol,
+ RpcClient rpcClient) {
+ this.serviceInstance = serviceInstance;
+ this.bootstrap = bootstrap;
+ this.protocol = protocol;
+ this.rpcClient = rpcClient;
+ }
+
+ @Override
+ public void updateChannel(Channel channel) {
+ }
+
+ // server push 模式下, 把client的clientName发送到server去
+ public void sendClientNameToServer(ChannelFuture channelFuture) {
+ RpcClientOptions rpcClientOptions = rpcClient.getRpcClientOptions();
+
+ RpcRequest r = new RpcRequest();
+ r.setChannel(channelFuture.channel());
+ r.setReadTimeoutMillis(10 * 1000);
+ r.setWriteTimeoutMillis(10 * 1000);
+ SPHead spHead = ((ServerPushProtocol) protocol).createSPHead();
+ spHead.setType(SPHead.TYPE_REGISTER_REQUEST); // 注册类型
+ r.setSpHead(spHead);
+
+ String serviceName = RegisterService.class.getName();
+ String methodName = "registerClient";
+ r.setServiceName(serviceName);
+ r.setMethodName(methodName);
+ RpcMethodInfo rpcMethodInfo = MethodUtils.getRpcMethodInfo(RegisterService.class, methodName);
+ r.setRpcMethodInfo(rpcMethodInfo);
+ r.setArgs(new Object[] {rpcClient.getRpcClientOptions().getClientName()});
+
+ // generate correlationId
+ RpcFuture registerRpcFuture = new RpcFuture();
+ long correlationId = FastFutureStore.getInstance(0).put(registerRpcFuture);
+ registerRpcFuture.setCorrelationId(correlationId);
+ // rpcFuture.setChannelInfo(channelInfo);
+ r.setCorrelationId(correlationId);
+
+ ByteBuf byteBuf;
+ try {
+ log.debug("send sendClientNameToServer, name:{}, correlationId:{}",
+ rpcClientOptions.getClientName(), r.getCorrelationId());
+ byteBuf = protocol.encodeRequest(r);
+ } catch (Exception e) {
+ log.error("send report packet to server, encode packet failed, msg:", e);
+ throw new RpcException(RpcException.SERIALIZATION_EXCEPTION, "rpc encode failed:", e);
+ }
+ channelFuture.channel().writeAndFlush(byteBuf);
+ }
+
+ @Override
+ public Channel connect(final String ip, final int port) {
+ final ChannelFuture future = bootstrap.connect(new InetSocketAddress(ip, port));
+ future.addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture channelFuture) throws Exception {
+ if (channelFuture.isSuccess()) {
+ log.debug("future callback, connect to {}:{} success, channel={}",
+ ip, port, channelFuture.channel());
+ // 发送clientName包到server
+ if (protocol instanceof ServerPushProtocol) {
+ sendClientNameToServer(future);
+ }
+ } else {
+ log.debug("future callback, connect to {}:{} failed due to {}",
+ ip, port, channelFuture.cause().getMessage());
+ }
+ }
+ });
+ future.syncUninterruptibly();
+ if (future.isSuccess()) {
+ return future.channel();
+ } else {
+ // throw exception when connect failed to the connection pool acquirer
+ log.error("connect to {}:{} failed, msg={}", ip, port, future.cause().getMessage());
+ throw new RpcException(future.cause());
+ }
+ }
+
+ @Override
+ public ServiceInstance getServiceInstance() {
+ return serviceInstance;
+ }
+
+ @Override
+ public long getFailedNum() {
+ return 0;
+ }
+
+ @Override
+ public void incFailedNum() {
+ }
+
+ @Override
+ public Queue getLatencyWindow() {
+ return null;
+ }
+
+ @Override
+ public void updateLatency(int latency) {
+ }
+
+ @Override
+ public void updateLatencyWithReadTimeOut() {
+ }
+
+ @Override
+ public Protocol getProtocol() {
+ return protocol;
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .append(serviceInstance.getIp())
+ .append(serviceInstance.getPort())
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ boolean flag = false;
+ if (object != null && BrpcChannel.class.isAssignableFrom(object.getClass())) {
+ BrpcChannel rhs = (BrpcChannel) object;
+ flag = new EqualsBuilder()
+ .append(serviceInstance.getIp(), rhs.getServiceInstance().getIp())
+ .append(serviceInstance.getPort(), rhs.getServiceInstance().getPort())
+ .isEquals();
+ }
+ return flag;
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcChannel.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcChannel.java
new file mode 100644
index 00000000..1d9f831b
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcChannel.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 Baidu, Inc. All Rights Reserved.
+ */
+
+package com.baidu.brpc.client.channel;
+
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+import com.baidu.brpc.client.instance.ServiceInstance;
+import com.baidu.brpc.protocol.Protocol;
+
+import io.netty.channel.Channel;
+
+public interface BrpcChannel {
+ Channel getChannel() throws Exception, NoSuchElementException, IllegalStateException;
+
+ void returnChannel(Channel channel);
+
+ void removeChannel(Channel channel);
+
+ void updateChannel(Channel channel);
+
+ void close();
+
+ Channel connect(final String ip, final int port);
+
+ ServiceInstance getServiceInstance();
+
+ long getFailedNum();
+
+ void incFailedNum();
+
+ Queue getLatencyWindow();
+
+ void updateLatency(int latency);
+
+ void updateLatencyWithReadTimeOut();
+
+ Protocol getProtocol();
+
+ void updateMaxConnection(int num);
+
+ int getCurrentMaxConnection();
+
+ int getActiveConnectionNum();
+
+ int getIdleConnectionNum();
+
+ int hashCode();
+
+ boolean equals(Object object);
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcChannelFactory.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcChannelFactory.java
new file mode 100644
index 00000000..2ba5e0ce
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcChannelFactory.java
@@ -0,0 +1,19 @@
+package com.baidu.brpc.client.channel;
+
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.client.instance.ServiceInstance;
+
+public class BrpcChannelFactory {
+ public static BrpcChannel createChannel(ServiceInstance instance, RpcClient rpcClient) {
+ ChannelType channelType = rpcClient.getRpcClientOptions().getChannelType();
+ if (channelType == ChannelType.POOLED_CONNECTION) {
+ return new BrpcPooledChannel(instance, rpcClient);
+ } else if (channelType == ChannelType.SINGLE_CONNECTION) {
+ return new BrpcSingleChannel(instance, rpcClient);
+ } else if (channelType == ChannelType.SHORT_CONNECTION) {
+ return new BrpcShortChannel(instance, rpcClient);
+ } else {
+ throw new IllegalArgumentException("channel type is not valid:" + channelType.getName());
+ }
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcPooledChannel.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcPooledChannel.java
new file mode 100644
index 00000000..41a39581
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcPooledChannel.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client.channel;
+
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.client.RpcClientOptions;
+import com.baidu.brpc.client.instance.ServiceInstance;
+import com.baidu.brpc.client.pool.ChannelPooledObjectFactory;
+
+import io.netty.channel.Channel;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * BrpcPooledChannelGroup class keeps fixed connections with one server
+ * Created by wenweihu86 on 2017/9/29.
+ */
+
+@Slf4j
+public class BrpcPooledChannel extends AbstractBrpcChannel {
+ private GenericObjectPool channelFuturePool;
+ /**
+ * failedNum only effect balanceStrategy
+ * thread-unsafe type can be accepted, so not use AtomicLong which may affect performance partly
+ */
+ private volatile long failedNum;
+ private int readTimeOut;
+ private int latencyWindowSize;
+ /**
+ * Used to save the rpc latency of the recent several rpc calls.
+ * This is for the fair load balance algorithm.
+ */
+ private Queue latencyWindow;
+ private RpcClientOptions rpcClientOptions;
+
+ public BrpcPooledChannel(ServiceInstance serviceInstance, RpcClient rpcClient) {
+ super(serviceInstance, rpcClient.getBootstrap(), rpcClient.getProtocol(), rpcClient);
+ this.protocol = rpcClient.getProtocol();
+ this.rpcClientOptions = rpcClient.getRpcClientOptions();
+ this.readTimeOut = rpcClientOptions.getReadTimeoutMillis();
+ this.latencyWindowSize = rpcClientOptions.getLatencyWindowSizeOfFairLoadBalance();
+ this.latencyWindow = new ConcurrentLinkedQueue();
+ GenericObjectPoolConfig conf = new GenericObjectPoolConfig();
+ // Maximum waiting time, when you need to borrow a connection, the maximum waiting time,
+ // if the time is exceeded, throw an exception, -1 is no time limit
+ conf.setMaxWaitMillis(rpcClientOptions.getConnectTimeoutMillis());
+ conf.setMaxTotal(rpcClientOptions.getMaxTotalConnections());
+ conf.setMaxIdle(rpcClientOptions.getMaxTotalConnections());
+ conf.setMinIdle(rpcClientOptions.getMinIdleConnections());
+ // Connect test when idle, start asynchronous evict thread for failure detection
+ conf.setTestWhileIdle(true);
+ // Maximum time for connection idle, testWhileIdle needs to be true
+ conf.setTimeBetweenEvictionRunsMillis(rpcClientOptions.getTimeBetweenEvictionRunsMillis());
+ channelFuturePool = new GenericObjectPool(new ChannelPooledObjectFactory(
+ this, serviceInstance.getIp(), serviceInstance.getPort()), conf);
+ try {
+ channelFuturePool.preparePool();
+ } catch (Exception ex) {
+ log.warn("init min idle object pool failed");
+ }
+ }
+
+ @Override
+ public Channel getChannel() throws Exception, NoSuchElementException, IllegalStateException {
+ return channelFuturePool.borrowObject();
+ }
+
+ @Override
+ public void returnChannel(Channel channel) {
+ try {
+ channelFuturePool.returnObject(channel);
+ } catch (Exception e) {
+ log.debug("return channel failed:{}", e.getMessage());
+ }
+ }
+
+ @Override
+ public void removeChannel(Channel channel) {
+ try {
+ channelFuturePool.invalidateObject(channel);
+ } catch (Exception e) {
+ log.debug("remove channel failed:{}", e.getMessage());
+ }
+ }
+
+ @Override
+ public void close() {
+ channelFuturePool.close();
+ }
+
+ @Override
+ public long getFailedNum() {
+ return failedNum;
+ }
+
+ @Override
+ public void incFailedNum() {
+ this.failedNum++;
+ }
+
+ @Override
+ public Queue getLatencyWindow() {
+ return latencyWindow;
+ }
+
+ @Override
+ public void updateLatency(int latency) {
+ latencyWindow.add(latency);
+ if (latencyWindow.size() > latencyWindowSize) {
+ latencyWindow.poll();
+ }
+ }
+
+
+ @Override
+ public void updateMaxConnection(int num) {
+
+ channelFuturePool.setMaxTotal(num);
+ channelFuturePool.setMaxIdle(num);
+
+ }
+
+ @Override
+ public int getCurrentMaxConnection() {
+ return channelFuturePool.getMaxTotal();
+ }
+
+ @Override
+ public int getActiveConnectionNum() {
+ return channelFuturePool.getNumActive();
+ }
+
+ @Override
+ public int getIdleConnectionNum() {
+ return channelFuturePool.getNumIdle();
+ }
+
+ @Override
+ public void updateLatencyWithReadTimeOut() {
+ updateLatency(readTimeOut);
+ }
+
+ public RpcClient getRpcClient() {
+ return rpcClient;
+ }
+
+ public void setRpcClient(RpcClient rpcClient) {
+ this.rpcClient = rpcClient;
+ }
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcShortChannel.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcShortChannel.java
new file mode 100644
index 00000000..16f4afcf
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcShortChannel.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client.channel;
+
+import java.util.NoSuchElementException;
+
+import com.baidu.brpc.ChannelInfo;
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.client.instance.ServiceInstance;
+
+import io.netty.channel.Channel;
+
+/**
+ * BrpcShortChannel build single & short connection with server
+ * and channel will be closed by brpc after communication with server
+ */
+public class BrpcShortChannel extends AbstractBrpcChannel {
+
+ public BrpcShortChannel(ServiceInstance instance, RpcClient rpcClient) {
+ super(instance, rpcClient.getBootstrap(), rpcClient.getProtocol(), rpcClient);
+ }
+
+ @Override
+ public Channel getChannel() throws Exception, NoSuchElementException, IllegalStateException {
+ Channel channel = connect(serviceInstance.getIp(), serviceInstance.getPort());
+ ChannelInfo channelInfo = ChannelInfo.getOrCreateClientChannelInfo(channel);
+ channelInfo.setProtocol(protocol);
+ channelInfo.setChannelGroup(this);
+ return channel;
+ }
+
+ @Override
+ public void returnChannel(Channel channel) {
+ }
+
+ @Override
+ public void removeChannel(Channel channel) {
+ channel.close();
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void updateMaxConnection(int num) {
+ }
+
+ @Override
+ public int getCurrentMaxConnection() {
+ return getActiveConnectionNum();
+ }
+
+ @Override
+ public int getActiveConnectionNum() {
+ return 0;
+ }
+
+ @Override
+ public int getIdleConnectionNum() {
+ return 0;
+ }
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcSingleChannel.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcSingleChannel.java
new file mode 100644
index 00000000..ec4015f1
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/BrpcSingleChannel.java
@@ -0,0 +1,225 @@
+package com.baidu.brpc.client.channel;
+
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.baidu.brpc.ChannelInfo;
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.client.RpcClientOptions;
+import com.baidu.brpc.client.instance.ServiceInstance;
+import com.baidu.brpc.utils.CustomThreadFactory;
+
+import io.netty.channel.Channel;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * BrpcSingleChannel class keeps single persistent connection with one server
+ */
+@Slf4j
+public class BrpcSingleChannel extends AbstractBrpcChannel {
+
+ private static final int RETRY_THRESHOLD = 2;
+
+ private volatile Channel channel;
+
+ private volatile Long lastTryConnectTime = 0L;
+ private AtomicInteger retryCount = new AtomicInteger(0);
+ private int connectPeriod;
+
+ private AtomicLong failedNum = new AtomicLong(0);
+ private int readTimeOut;
+ private int latencyWindowSize;
+ private Queue latencyWindow;
+
+
+ private static final ExecutorService CONNECTION_SERVICE = Executors.newFixedThreadPool(3, new CustomThreadFactory(
+ "single-channel-connect-thread"));
+
+ public static class ReConnectTask implements Runnable {
+ BrpcSingleChannel channelGroup;
+ Channel oldChannel;
+
+ public ReConnectTask(BrpcSingleChannel singleChannelGroup, Channel oldChannel) {
+ this.channelGroup = singleChannelGroup;
+ this.oldChannel = oldChannel;
+ }
+
+ @Override
+ public void run() {
+ if (oldChannel != channelGroup.channel) {
+ return;
+ }
+ // avoid busy connecting
+ if (System.currentTimeMillis() - channelGroup.lastTryConnectTime < channelGroup.connectPeriod
+ && channelGroup.retryCount.get() >= RETRY_THRESHOLD) {
+ return;
+ }
+ synchronized (channelGroup) {
+ if (oldChannel != channelGroup.channel) {
+ return;
+ }
+ Channel newChannel = null;
+ try {
+ newChannel = channelGroup.createChannel(
+ channelGroup.getServiceInstance().getIp(),
+ channelGroup.getServiceInstance().getPort());
+ } catch (Exception e) {
+ log.info("failed reconnecting");
+ }
+ if (newChannel != null) {
+ channelGroup.updateChannel(newChannel);
+ if (oldChannel != null) {
+ oldChannel.close();
+ }
+ }
+ }
+ }
+ }
+
+ public BrpcSingleChannel(ServiceInstance serviceInstance, RpcClient rpcClient) {
+ super(serviceInstance, rpcClient.getBootstrap(), rpcClient.getProtocol(), rpcClient);
+ RpcClientOptions rpcClientOptions = rpcClient.getRpcClientOptions();
+ this.connectPeriod = rpcClientOptions.getHealthyCheckIntervalMillis();
+ this.readTimeOut = rpcClientOptions.getReadTimeoutMillis();
+ this.latencyWindowSize = rpcClientOptions.getLatencyWindowSizeOfFairLoadBalance();
+ this.latencyWindow = new ConcurrentLinkedQueue();
+ }
+
+ @Override
+ public Channel getChannel() throws Exception, NoSuchElementException, IllegalStateException {
+ if (isNonActive(channel)) {
+ synchronized (this) {
+ if (isNonActive(channel)) {
+ channel = createChannel(serviceInstance.getIp(), serviceInstance.getPort());
+ }
+ }
+ }
+ return channel;
+ }
+
+ @Override
+ public void removeChannel(Channel channel) {
+ if (channel != this.channel) {
+ return;
+ }
+ CONNECTION_SERVICE.execute(genReconnectTask(channel));
+ }
+
+ @Override
+ public void updateChannel(Channel channel) {
+ if (channel != this.channel) {
+ this.channel = channel;
+ }
+ }
+
+ private ReConnectTask genReconnectTask(Channel oldChannel) {
+ return new ReConnectTask(this, oldChannel);
+ }
+
+ private Channel createChannel(String ip, int port) {
+ long currentTimeMillis = System.currentTimeMillis();
+ if (currentTimeMillis - lastTryConnectTime < connectPeriod
+ && retryCount.getAndIncrement() >= RETRY_THRESHOLD) {
+ return null;
+ } else {
+ if (currentTimeMillis - lastTryConnectTime >= connectPeriod) {
+ refreshConnectionState(currentTimeMillis, 1);
+ }
+ Channel channel;
+ channel = doCreateChannel(ip, port);
+ refreshConnectionState(currentTimeMillis, 0);
+ return channel;
+ }
+ }
+
+ private void refreshConnectionState(long currentTimeMillis, int retryCount) {
+ this.retryCount = new AtomicInteger(retryCount);
+ lastTryConnectTime = currentTimeMillis;
+ }
+
+ private Channel doCreateChannel(String ip, int port) {
+ Channel channel = connect(ip, port);
+ ChannelInfo channelInfo = ChannelInfo.getOrCreateClientChannelInfo(channel);
+ channelInfo.setProtocol(protocol);
+ channelInfo.setChannelGroup(this);
+ return channel;
+ }
+
+ @Override
+ public void close() {
+ if (channel != null) {
+ channel.close();
+ channel = null;
+ }
+ }
+
+ @Override
+ public int getCurrentMaxConnection() {
+ return countChannel();
+ }
+
+ @Override
+ public int getActiveConnectionNum() {
+ return countChannel();
+ }
+
+ @Override
+ public int getIdleConnectionNum() {
+ return countChannel();
+ }
+
+ @Override
+ public void returnChannel(Channel channel) {
+ // ignore
+ }
+
+ @Override
+ public void updateMaxConnection(int num) {
+ // ignore
+ }
+
+ private boolean isActive(Channel channel) {
+ return channel != null && channel.isActive();
+ }
+
+ private boolean isNonActive(Channel channel) {
+ return !isActive(channel);
+ }
+
+ private int countChannel() {
+ return isActive(channel) ? 1 : 0;
+ }
+
+ @Override
+ public long getFailedNum() {
+ return failedNum.get();
+ }
+
+ @Override
+ public void incFailedNum() {
+ failedNum.incrementAndGet();
+ }
+
+ @Override
+ public Queue getLatencyWindow() {
+ return latencyWindow;
+ }
+
+ @Override
+ public void updateLatency(int latency) {
+ latencyWindow.add(latency);
+ if (latencyWindow.size() > latencyWindowSize) {
+ latencyWindow.poll();
+ }
+ }
+
+ @Override
+ public void updateLatencyWithReadTimeOut() {
+ updateLatency(readTimeOut);
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/ChannelType.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/ChannelType.java
new file mode 100644
index 00000000..578e7031
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/channel/ChannelType.java
@@ -0,0 +1,24 @@
+package com.baidu.brpc.client.channel;
+
+public enum ChannelType {
+
+ POOLED_CONNECTION(0, "POOLED_CONNECTION"),
+ SINGLE_CONNECTION(1, "SINGLE_CONNECTION"),
+ SHORT_CONNECTION(2, "SHORT_CONNECTION");
+
+ private int id;
+ private String name;
+
+ ChannelType(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/ClientWorkTask.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/ClientWorkTask.java
new file mode 100644
index 00000000..114ab28f
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/ClientWorkTask.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client.handler;
+
+import java.lang.reflect.Method;
+
+import com.baidu.brpc.RpcContext;
+import com.baidu.brpc.RpcMethodInfo;
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.client.RpcFuture;
+import com.baidu.brpc.protocol.Protocol;
+import com.baidu.brpc.protocol.Request;
+import com.baidu.brpc.protocol.Response;
+import com.baidu.brpc.protocol.push.SPHead;
+import com.baidu.brpc.protocol.push.ServerPushPacket;
+import com.baidu.brpc.protocol.push.ServerPushProtocol;
+import com.baidu.brpc.server.ServiceManager;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Setter
+@Getter
+@AllArgsConstructor
+public class ClientWorkTask implements Runnable {
+ private RpcClient rpcClient;
+ private Object packet;
+ private Protocol protocol;
+ private ChannelHandlerContext ctx;
+
+ @Override
+ public void run() {
+ // 只有server push协议下,有可能受到request类型
+ if (protocol instanceof ServerPushProtocol) {
+ // 区分类型
+ SPHead spHead = ((ServerPushPacket) packet).getSpHead();
+ if (spHead.getType() == SPHead.TYPE_PUSH_REQUEST) {
+ handlePushRequest();
+ return;
+ }
+ }
+
+ Response response;
+ try {
+ response = protocol.decodeResponse(packet, ctx);
+ } catch (Exception e) {
+ log.warn("decode response failed:", e);
+ return;
+ }
+
+ if (response.getRpcFuture() != null) {
+ log.debug("handle response, correlationId={}", response.getCorrelationId());
+ RpcFuture future = response.getRpcFuture();
+ future.handleResponse(response);
+ } else {
+ log.warn("rpcFuture is null, server return to slow, correlationId={}", response.getCorrelationId());
+ }
+ }
+
+ /**
+ * 收到push请求的处理方法
+ */
+ private void handlePushRequest() {
+
+ Request request = null;
+ Response response = protocol.createResponse();
+ try {
+ request = protocol.decodeRequest(packet);
+ } catch (Exception ex) {
+ // throw request
+ log.warn("decode request failed:", ex);
+ response.setException(ex);
+ } finally {
+ if (request != null && request.getException() != null) {
+ response.setException(request.getException());
+ }
+ }
+
+ RpcContext rpcContext = null;
+ request.setChannel(ctx.channel());
+ if (request.getBinaryAttachment() != null
+ || request.getKvAttachment() != null) {
+ rpcContext = RpcContext.getContext();
+ if (request.getBinaryAttachment() != null) {
+ rpcContext.setRequestBinaryAttachment(request.getBinaryAttachment());
+ }
+ if (request.getKvAttachment() != null) {
+ rpcContext.setRequestKvAttachment(request.getKvAttachment());
+ }
+ rpcContext.setRemoteAddress(ctx.channel().remoteAddress());
+ }
+
+ response.setLogId(request.getLogId());
+ response.setCorrelationId(request.getCorrelationId());
+ response.setCompressType(request.getCompressType());
+ response.setException(request.getException());
+ response.setRpcMethodInfo(request.getRpcMethodInfo());
+
+ String serviceName = request.getServiceName();
+ String methodName = request.getMethodName();
+ RpcMethodInfo service = ServiceManager.getInstance().getService(serviceName, methodName);
+ Method targetMethod = request.getTargetMethod();
+ Object t = service.getTarget();
+ Object result = null;
+ try {
+ result = targetMethod.invoke(t, request.getArgs());
+ } catch (Exception e) {
+ log.error("exception :", e);
+ }
+ response.setResult(result);
+ try {
+ ByteBuf byteBuf = protocol.encodeResponse(request, response);
+ ChannelFuture channelFuture = ctx.channel().writeAndFlush(byteBuf);
+ protocol.afterResponseSent(request, response, channelFuture);
+ } catch (Exception ex) {
+ log.warn("send response failed:", ex);
+ }
+
+ if (rpcContext != null) {
+ rpcContext.reset();
+ }
+ }
+
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/IdleChannelHandler.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/IdleChannelHandler.java
new file mode 100644
index 00000000..e141db53
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/IdleChannelHandler.java
@@ -0,0 +1,20 @@
+package com.baidu.brpc.client.handler;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.IdleStateEvent;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class IdleChannelHandler extends ChannelInboundHandlerAdapter {
+
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ if (evt instanceof IdleStateEvent) {
+ Channel channel = ctx.channel();
+ channel.close();
+ } else {
+ super.userEventTriggered(ctx, evt);
+ }
+ }
+}
diff --git a/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/RpcClientHandler.java b/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/RpcClientHandler.java
new file mode 100644
index 00000000..f70da35c
--- /dev/null
+++ b/brpc-java-core/src/main/java/com/baidu/brpc/client/handler/RpcClientHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed 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 com.baidu.brpc.client.handler;
+
+import com.baidu.brpc.ChannelInfo;
+import com.baidu.brpc.client.RpcClient;
+import com.baidu.brpc.exceptions.BadSchemaException;
+import com.baidu.brpc.exceptions.NotEnoughDataException;
+import com.baidu.brpc.exceptions.RpcException;
+import com.baidu.brpc.exceptions.TooBigDataException;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import lombok.extern.slf4j.Slf4j;
+
+@ChannelHandler.Sharable
+@Slf4j
+public class RpcClientHandler extends SimpleChannelInboundHandler