Compare commits
32 Commits
Author | SHA1 | Date |
---|---|---|
David M. Carr | 97286d34d6 | 11 years ago |
Bob T Builder | 093e73bf06 | 11 years ago |
Bob T Builder | 5ba9cb2bce | 11 years ago |
adriancole | 151e81dd6d | 11 years ago |
adriancole | a113e19e76 | 11 years ago |
adriancole | 5099bdcf2e | 11 years ago |
David M. Carr | 0dd978275b | 11 years ago |
David M. Carr | f1cea1cca5 | 11 years ago |
Bob T Builder | 2af2658209 | 11 years ago |
Bob T Builder | 092e093d6b | 11 years ago |
adriancole | 073e54ee39 | 11 years ago |
adriancole | c6b2a5918a | 11 years ago |
adriancole | 26d04ecc01 | 11 years ago |
adriancole | dd02b9b3fd | 11 years ago |
adriancole | 22d1046b88 | 11 years ago |
Bob T Builder | ad9ea726ad | 11 years ago |
Bob T Builder | bfbbd8e929 | 11 years ago |
adriancole | fb8f2ac506 | 11 years ago |
adriancole | 27d936a41d | 11 years ago |
adriancole | 73efa61617 | 11 years ago |
adriancole | facf514f66 | 11 years ago |
Bob T Builder | 445cbd9da1 | 11 years ago |
Bob T Builder | 4f2166acfb | 11 years ago |
adriancole | 950b976241 | 11 years ago |
adriancole | 321210d3ee | 11 years ago |
adriancole | 15b48c1b71 | 11 years ago |
adriancole | f21b1a0bf3 | 11 years ago |
adriancole | 9f77cb5f4c | 11 years ago |
adriancole | 08365abcfc | 11 years ago |
adriancole | aec2234d60 | 11 years ago |
Bob T Builder | cd40bacfeb | 11 years ago |
Bob T Builder | e0efd41940 | 11 years ago |
76 changed files with 1633 additions and 553 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
Feign |
||||
Copyright 2013 Netflix, Inc. |
||||
|
||||
Portions of this software developed by Commerce Technologies, Inc. |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign; |
||||
|
||||
/** |
||||
* Zero or more {@code RequestInterceptors} may be configured for purposes |
||||
* such as adding headers to all requests. No guarantees are give with regards |
||||
* to the order that interceptors are applied. Once interceptors are applied, |
||||
* {@link Target#apply(RequestTemplate)} is called to create the immutable http |
||||
* request sent via {@link Client#execute(Request, feign.Request.Options)}. |
||||
* <br> |
||||
* <br> |
||||
* For example: |
||||
* <br> |
||||
* <pre> |
||||
* public void apply(RequestTemplate input) { |
||||
* input.replaceHeader("X-Auth", currentToken); |
||||
* } |
||||
* </pre> |
||||
* <br> |
||||
* <br><b>Configuration</b><br> |
||||
* <br> |
||||
* {@code RequestInterceptors} are configured via Dagger |
||||
* {@link dagger.Provides.Type#SET set} or |
||||
* {@link dagger.Provides.Type#SET_VALUES set values} |
||||
* {@link dagger.Provides provider} methods. |
||||
* <br> |
||||
* <br> |
||||
* For example: |
||||
* <br> |
||||
* <pre> |
||||
* {@literal @}Provides(Type = SET) RequestInterceptor addTimestamp(TimestampInterceptor in) { |
||||
* return in; |
||||
* } |
||||
* </pre> |
||||
* <br> |
||||
* <br><b>Implementation notes</b><br> |
||||
* <br> |
||||
* Do not add parameters, such as {@code /path/{foo}/bar } |
||||
* in your implementation of {@link #apply(RequestTemplate)}. |
||||
* <br> |
||||
* Interceptors are applied after the template's parameters are |
||||
* {@link RequestTemplate#resolve(java.util.Map) resolved}. This is to ensure |
||||
* that you can implement signatures are interceptors. |
||||
* <br> |
||||
* <br><br><b>Relationship to Retrofit 1.x</b><br> |
||||
* <br> |
||||
* This class is similar to {@code RequestInterceptor.intercept()}, |
||||
* except that the implementation can read, remove, or otherwise mutate any |
||||
* part of the request template. |
||||
*/ |
||||
public interface RequestInterceptor { |
||||
/** |
||||
* Called for every request. Add data using methods on the supplied {@link RequestTemplate}. |
||||
*/ |
||||
void apply(RequestTemplate template); |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign; |
||||
|
||||
import javax.net.ssl.HostnameVerifier; |
||||
import javax.net.ssl.SSLSession; |
||||
|
||||
final class AcceptAllHostnameVerifier implements HostnameVerifier { |
||||
@Override |
||||
public boolean verify(String s, SSLSession sslSession) { |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign; |
||||
|
||||
import com.google.common.io.InputSupplier; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.util.zip.GZIPInputStream; |
||||
|
||||
class GZIPStreams { |
||||
static InputSupplier<GZIPInputStream> newInputStreamSupplier(InputSupplier<? extends InputStream> supplier) { |
||||
return new GZIPInputStreamSupplier(supplier); |
||||
} |
||||
|
||||
private static class GZIPInputStreamSupplier implements InputSupplier<GZIPInputStream> { |
||||
private final InputSupplier<? extends InputStream> supplier; |
||||
|
||||
GZIPInputStreamSupplier(InputSupplier<? extends InputStream> supplier) { |
||||
this.supplier = supplier; |
||||
} |
||||
|
||||
@Override |
||||
public GZIPInputStream getInput() throws IOException { |
||||
return new GZIPInputStream(supplier.getInput()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,193 @@
@@ -0,0 +1,193 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign; |
||||
|
||||
import com.google.common.cache.CacheBuilder; |
||||
import com.google.common.cache.CacheLoader; |
||||
import com.google.common.cache.LoadingCache; |
||||
import com.google.common.io.Closer; |
||||
import com.google.common.io.InputSupplier; |
||||
import com.google.common.io.Resources; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.net.InetAddress; |
||||
import java.net.Socket; |
||||
import java.security.KeyStore; |
||||
import java.security.Principal; |
||||
import java.security.PrivateKey; |
||||
import java.security.SecureRandom; |
||||
import java.security.cert.Certificate; |
||||
import java.security.cert.X509Certificate; |
||||
import java.util.Arrays; |
||||
|
||||
import javax.inject.Provider; |
||||
import javax.net.ssl.KeyManager; |
||||
import javax.net.ssl.SSLContext; |
||||
import javax.net.ssl.SSLSocket; |
||||
import javax.net.ssl.SSLSocketFactory; |
||||
import javax.net.ssl.TrustManager; |
||||
import javax.net.ssl.X509KeyManager; |
||||
import javax.net.ssl.X509TrustManager; |
||||
|
||||
import static com.google.common.base.Throwables.propagate; |
||||
|
||||
/** |
||||
* Used for ssl tests to simplify setup. |
||||
*/ |
||||
final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509TrustManager, X509KeyManager { |
||||
|
||||
private static LoadingCache<String, SSLSocketFactory> sslSocketFactories = |
||||
CacheBuilder.newBuilder().build(new CacheLoader<String, SSLSocketFactory>() { |
||||
@Override |
||||
public SSLSocketFactory load(String serverAlias) throws Exception { |
||||
return new TrustingSSLSocketFactory(serverAlias); |
||||
} |
||||
}); |
||||
|
||||
public static SSLSocketFactory get() { |
||||
return get(""); |
||||
} |
||||
|
||||
public static SSLSocketFactory get(String serverAlias) { |
||||
return sslSocketFactories.getUnchecked(serverAlias); |
||||
} |
||||
|
||||
private static final char[] KEYSTORE_PASSWORD = "password".toCharArray(); |
||||
|
||||
private final SSLSocketFactory delegate; |
||||
private final String serverAlias; |
||||
private final PrivateKey privateKey; |
||||
private final X509Certificate[] certificateChain; |
||||
|
||||
private TrustingSSLSocketFactory(String serverAlias) { |
||||
try { |
||||
SSLContext sc = SSLContext.getInstance("SSL"); |
||||
sc.init(new KeyManager[]{this}, new TrustManager[]{this}, new SecureRandom()); |
||||
this.delegate = sc.getSocketFactory(); |
||||
} catch (Exception e) { |
||||
throw propagate(e); |
||||
} |
||||
this.serverAlias = serverAlias; |
||||
if (serverAlias.isEmpty()) { |
||||
this.privateKey = null; |
||||
this.certificateChain = null; |
||||
} else { |
||||
try { |
||||
KeyStore keyStore = loadKeyStore(Resources.newInputStreamSupplier(Resources.getResource("keystore.jks"))); |
||||
this.privateKey = (PrivateKey) keyStore.getKey(serverAlias, KEYSTORE_PASSWORD); |
||||
Certificate[] rawChain = keyStore.getCertificateChain(serverAlias); |
||||
this.certificateChain = Arrays.copyOf(rawChain, rawChain.length, X509Certificate[].class); |
||||
} catch (Exception e) { |
||||
throw propagate(e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override public String[] getDefaultCipherSuites() { |
||||
return ENABLED_CIPHER_SUITES; |
||||
} |
||||
|
||||
@Override public String[] getSupportedCipherSuites() { |
||||
return ENABLED_CIPHER_SUITES; |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(s, host, port, autoClose)); |
||||
} |
||||
|
||||
static Socket setEnabledCipherSuites(Socket socket) { |
||||
SSLSocket.class.cast(socket).setEnabledCipherSuites(ENABLED_CIPHER_SUITES); |
||||
return socket; |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(String host, int port) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(host, port)); |
||||
} |
||||
|
||||
@Override public Socket createSocket(InetAddress host, int port) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(host, port)); |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(host, port, localHost, localPort)); |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) |
||||
throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(address, port, localAddress, localPort)); |
||||
} |
||||
|
||||
public X509Certificate[] getAcceptedIssuers() { |
||||
return null; |
||||
} |
||||
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) { |
||||
} |
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) { |
||||
} |
||||
|
||||
@Override |
||||
public String[] getClientAliases(String keyType, Principal[] issuers) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public String[] getServerAliases(String keyType, Principal[] issuers) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { |
||||
return serverAlias; |
||||
} |
||||
|
||||
@Override |
||||
public X509Certificate[] getCertificateChain(String alias) { |
||||
return certificateChain; |
||||
} |
||||
|
||||
@Override |
||||
public PrivateKey getPrivateKey(String alias) { |
||||
return privateKey; |
||||
} |
||||
|
||||
private static KeyStore loadKeyStore(InputSupplier<InputStream> inputStreamSupplier) throws IOException { |
||||
Closer closer = Closer.create(); |
||||
try { |
||||
InputStream inputStream = closer.register(inputStreamSupplier.getInput()); |
||||
KeyStore keyStore = KeyStore.getInstance("JKS"); |
||||
keyStore.load(inputStream, KEYSTORE_PASSWORD); |
||||
return keyStore; |
||||
} catch (Throwable e) { |
||||
throw closer.rethrow(e); |
||||
} finally { |
||||
closer.close(); |
||||
} |
||||
} |
||||
|
||||
private final static String[] ENABLED_CIPHER_SUITES = {"SSL_RSA_WITH_RC4_128_MD5"}; |
||||
} |
Binary file not shown.
@ -0,0 +1,178 @@
@@ -0,0 +1,178 @@
|
||||
// Manages classpath and IDE annotation processing config for dagger. |
||||
// |
||||
// setup: |
||||
// Add the following to your root build.gradle |
||||
// |
||||
// apply plugin: 'idea' |
||||
// subprojects { |
||||
// apply from: rootProject.file('dagger.gradle') |
||||
// } |
||||
// |
||||
// do not use gradle integration of the ide. instead generate and import like so: |
||||
// |
||||
// ./gradlew clean cleanEclipse cleanIdea eclipse idea |
||||
// |
||||
// known limitations: |
||||
// as output folders include generated classes, you may need to run clean a few times. |
||||
// incompatible with android plugin as it applies the java plugin |
||||
// unnecessarily applies both eclipse and idea plugins even if you don't use them |
||||
// suffers from the normal non-IDE eclipse integration where nested projects don't import properly. |
||||
// change your structure to flattened to avoid this. |
||||
// |
||||
// deprecated by: https://github.com/Netflix/gradle-template/issues/8 |
||||
// |
||||
// original design: cfieber |
||||
apply plugin: 'java' |
||||
apply plugin: 'eclipse' |
||||
apply plugin: 'idea' |
||||
|
||||
if (!project.hasProperty('daggerVersion')) { |
||||
ext { |
||||
daggerVersion = "1.1.0" |
||||
} |
||||
} |
||||
|
||||
configurations { |
||||
daggerCompiler { |
||||
visible false |
||||
} |
||||
} |
||||
|
||||
configurations.all { |
||||
resolutionStrategy { |
||||
eachDependency { DependencyResolveDetails details -> |
||||
if (details.requested.group == 'com.squareup.dagger') { |
||||
details.useVersion daggerVersion |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
def annotationGeneratedSources = file('.generated/src') |
||||
def annotationGeneratedTestSources = file('.generated/test') |
||||
|
||||
task prepareAnnotationGeneratedSourceDirs(overwrite: true) << { |
||||
annotationGeneratedSources.mkdirs() |
||||
annotationGeneratedTestSources.mkdirs() |
||||
sourceSets*.java.srcDirs*.each { it.mkdirs() } |
||||
sourceSets*.resources.srcDirs*.each { it.mkdirs() } |
||||
} |
||||
|
||||
sourceSets { |
||||
main { |
||||
java { |
||||
compileClasspath += configurations.daggerCompiler |
||||
} |
||||
} |
||||
test { |
||||
java { |
||||
compileClasspath += configurations.daggerCompiler |
||||
} |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
compile "com.squareup.dagger:dagger:${project.daggerVersion}" |
||||
daggerCompiler "com.squareup.dagger:dagger-compiler:${project.daggerVersion}" |
||||
} |
||||
|
||||
rootProject.idea.project.ipr.withXml { projectXml -> |
||||
projectXml.asNode().component.find { it.@name == 'CompilerConfiguration' }.annotationProcessing[0].replaceNode { |
||||
annotationProcessing { |
||||
profile(default: true, name: 'Default', enabled: true) { |
||||
sourceOutputDir name: relativePath(annotationGeneratedSources) |
||||
sourceTestOutputDir name: relativePath(annotationGeneratedTestSources) |
||||
outputRelativeToContentRoot value: true |
||||
processorPath useClasspath: true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
tasks.ideaModule.dependsOn(prepareAnnotationGeneratedSourceDirs) |
||||
|
||||
idea.module { |
||||
scopes.PROVIDED.plus += project.configurations.daggerCompiler |
||||
iml.withXml { xml-> |
||||
def moduleSource = xml.asNode().component.find { it.@name = 'NewModuleRootManager' }.content[0] |
||||
moduleSource.appendNode('sourceFolder', [url: "file://\$MODULE_DIR\$/${relativePath(annotationGeneratedSources)}", isTestSource: false]) |
||||
moduleSource.appendNode('sourceFolder', [url: "file://\$MODULE_DIR\$/${relativePath(annotationGeneratedTestSources)}", isTestSource: true]) |
||||
} |
||||
} |
||||
|
||||
tasks.eclipseClasspath.dependsOn(prepareAnnotationGeneratedSourceDirs) |
||||
|
||||
eclipse.classpath { |
||||
plusConfigurations += project.configurations.daggerCompiler |
||||
} |
||||
|
||||
tasks.eclipseClasspath { |
||||
doLast { |
||||
eclipse.classpath.file.withXml { |
||||
it.asNode().children()[0] + { |
||||
classpathentry(kind: 'src', path: relativePath(annotationGeneratedSources)) { |
||||
attributes { |
||||
attribute name: 'optional', value: true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// http://forums.gradle.org/gradle/topics/eclipse_generated_files_should_be_put_in_the_same_place_as_the_gradle_generated_files |
||||
Map pathMappings = [:]; |
||||
SourceSetContainer sourceSets = project.sourceSets; |
||||
sourceSets.each { SourceSet sourceSet -> |
||||
String relativeJavaOutputDirectory = project.relativePath(sourceSet.output.classesDir); |
||||
String relativeResourceOutputDirectory = project.relativePath(sourceSet.output.resourcesDir); |
||||
sourceSet.java.getSrcDirTrees().each { DirectoryTree sourceDirectory -> |
||||
String relativeSrcPath = project.relativePath(sourceDirectory.dir.absolutePath); |
||||
|
||||
pathMappings[relativeSrcPath] = relativeJavaOutputDirectory; |
||||
} |
||||
sourceSet.resources.getSrcDirTrees().each { DirectoryTree resourceDirectory -> |
||||
String relativeResourcePath = project.relativePath(resourceDirectory.dir.absolutePath); |
||||
|
||||
pathMappings[relativeResourcePath] = relativeResourceOutputDirectory; |
||||
} |
||||
} |
||||
|
||||
project.eclipse.classpath.file { |
||||
whenMerged { classpath -> |
||||
classpath.entries.findAll { entry -> |
||||
return entry.kind == 'src'; |
||||
}.each { entry -> |
||||
if(pathMappings.containsKey(entry.path)) { |
||||
entry.output = pathMappings[entry.path]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
eclipse.jdt.file.withProperties { props -> |
||||
props.setProperty('org.eclipse.jdt.core.compiler.processAnnotations', 'enabled') |
||||
} |
||||
|
||||
tasks.eclipseJdt { |
||||
doFirst { |
||||
def aptPrefs = file('.settings/org.eclipse.jdt.apt.core.prefs') |
||||
aptPrefs.parentFile.mkdirs() |
||||
|
||||
aptPrefs.text = """\ |
||||
eclipse.preferences.version=1 |
||||
org.eclipse.jdt.apt.aptEnabled=true |
||||
org.eclipse.jdt.apt.genSrcDir=${relativePath(annotationGeneratedSources)} |
||||
org.eclipse.jdt.apt.reconcileEnabled=true |
||||
""".stripIndent() |
||||
|
||||
file('.factorypath').withWriter { |
||||
new groovy.xml.MarkupBuilder(it).'factorypath' { |
||||
project.configurations.daggerCompiler.files.each { dep -> |
||||
'factorypathentry' kind: 'EXTJAR', id: dep.absolutePath, enabled: true, runInBatchMode: false |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
apply plugin: 'java' |
||||
|
||||
dependencies { |
||||
compile 'com.netflix.feign:feign-core:4.1.0' |
||||
compile 'com.netflix.feign:feign-gson:4.1.0' |
||||
provided 'com.squareup.dagger:dagger-compiler:1.1.0' |
||||
} |
||||
|
||||
// create a self-contained jar that is executable |
||||
// the output is both a 'fat' project artifact and |
||||
// a convenience file named "build/github" |
||||
task fatJar(dependsOn: classes, type: Jar) { |
||||
classifier 'fat' |
||||
|
||||
doFirst { |
||||
// Delay evaluation until the compile configuration is ready |
||||
from { |
||||
configurations.compile.collect { zipTree(it) } |
||||
} |
||||
} |
||||
|
||||
from (sourceSets*.output.classesDir) { |
||||
} |
||||
|
||||
// really executable jar |
||||
// http://skife.org/java/unix/2011/06/20/really_executable_jars.html |
||||
|
||||
manifest { |
||||
attributes 'Main-Class': 'feign.example.wikipedia.WikipediaExample' |
||||
} |
||||
|
||||
// for convenience, we make a file in the build dir named github with no extension |
||||
doLast { |
||||
def srcFile = new File("${buildDir}/libs/${archiveName}") |
||||
def shortcutFile = new File("${buildDir}/wikipedia") |
||||
shortcutFile.delete() |
||||
shortcutFile << "#!/usr/bin/env sh\n" |
||||
shortcutFile << 'exec java -jar $0 "$@"' + "\n" |
||||
shortcutFile << srcFile.bytes |
||||
shortcutFile.setExecutable(true, true) |
||||
srcFile.delete() |
||||
srcFile << shortcutFile.bytes |
||||
srcFile.setExecutable(true, true) |
||||
} |
||||
} |
||||
|
||||
artifacts { |
||||
archives fatJar |
||||
} |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
package feign.example.wikipedia; |
||||
|
||||
import com.google.gson.stream.JsonReader; |
||||
import feign.codec.Decoder; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.Reader; |
||||
import java.lang.reflect.Type; |
||||
|
||||
abstract class ResponseDecoder<X> implements Decoder.TextStream<WikipediaExample.Response<X>> { |
||||
|
||||
/** |
||||
* name of the key inside the {@code query} dict which holds the elements desired. ex. {@code pages}. |
||||
*/ |
||||
protected abstract String query(); |
||||
|
||||
/** |
||||
* Parses the contents of a result object. |
||||
* <p/> |
||||
* <br> |
||||
* ex. If {@link #query()} is {@code pages}, then this would parse the value of each key in the dict {@code pages}. |
||||
* In the example below, this would first start at line {@code 3}. |
||||
* <p/> |
||||
* <pre> |
||||
* "pages": { |
||||
* "2576129": { |
||||
* "pageid": 2576129, |
||||
* "title": "Burchell's zebra", |
||||
* --snip-- |
||||
* </pre> |
||||
*/ |
||||
protected abstract X build(JsonReader reader) throws IOException; |
||||
|
||||
/** |
||||
* the wikipedia api doesn't use json arrays, rather a series of nested objects. |
||||
*/ |
||||
@Override |
||||
public WikipediaExample.Response<X> decode(Reader ireader, Type type) throws IOException { |
||||
WikipediaExample.Response<X> pages = new WikipediaExample.Response<X>(); |
||||
JsonReader reader = new JsonReader(ireader); |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
String nextName = reader.nextName(); |
||||
if ("query".equals(nextName)) { |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
if (query().equals(reader.nextName())) { |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
// each element is in form: "id" : { object }
|
||||
// this advances the pointer to the value and skips the key
|
||||
reader.nextName(); |
||||
reader.beginObject(); |
||||
pages.add(build(reader)); |
||||
reader.endObject(); |
||||
} |
||||
reader.endObject(); |
||||
} else { |
||||
reader.skipValue(); |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
} else if ("query-continue".equals(nextName)) { |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
if ("search".equals(reader.nextName())) { |
||||
reader.beginObject(); |
||||
while (reader.hasNext()) { |
||||
if ("gsroffset".equals(reader.nextName())) { |
||||
pages.nextOffset = reader.nextLong(); |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
} else { |
||||
reader.skipValue(); |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
} else { |
||||
reader.skipValue(); |
||||
} |
||||
} |
||||
reader.endObject(); |
||||
reader.close(); |
||||
return pages; |
||||
} |
||||
} |
@ -0,0 +1,145 @@
@@ -0,0 +1,145 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign.example.wikipedia; |
||||
|
||||
import com.google.gson.stream.JsonReader; |
||||
import dagger.Module; |
||||
import dagger.Provides; |
||||
import feign.Feign; |
||||
import feign.Logger; |
||||
import feign.RequestLine; |
||||
import feign.codec.Decoder; |
||||
import feign.gson.GsonModule; |
||||
|
||||
import javax.inject.Named; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Iterator; |
||||
|
||||
import static dagger.Provides.Type.SET; |
||||
import static feign.Logger.ErrorLogger; |
||||
import static feign.Logger.Level.BASIC; |
||||
|
||||
public class WikipediaExample { |
||||
|
||||
public static interface Wikipedia { |
||||
@RequestLine("GET /w/api.php?action=query&generator=search&prop=info&format=json&gsrsearch={search}") |
||||
Response<Page> search(@Named("search") String search); |
||||
|
||||
@RequestLine("GET /w/api.php?action=query&generator=search&prop=info&format=json&gsrsearch={search}&gsroffset={offset}") |
||||
Response<Page> resumeSearch(@Named("search") String search, @Named("offset") long offset); |
||||
} |
||||
|
||||
static class Page { |
||||
long id; |
||||
String title; |
||||
} |
||||
|
||||
public static class Response<X> extends ArrayList<X> { |
||||
/** |
||||
* when present, the position to resume the list. |
||||
*/ |
||||
Long nextOffset; |
||||
} |
||||
|
||||
public static void main(String... args) throws InterruptedException { |
||||
Wikipedia wikipedia = Feign.create(Wikipedia.class, "http://en.wikipedia.org", new WikipediaModule()); |
||||
|
||||
System.out.println("Let's search for PTAL!"); |
||||
Iterator<Page> pages = lazySearch(wikipedia, "PTAL"); |
||||
while (pages.hasNext()) { |
||||
System.out.println(pages.next().title); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* this will lazily continue searches, making new http calls as necessary. |
||||
* |
||||
* @param wikipedia used to search |
||||
* @param query see {@link Wikipedia#search(String)}. |
||||
*/ |
||||
static Iterator<Page> lazySearch(final Wikipedia wikipedia, final String query) { |
||||
final Response<Page> first = wikipedia.search(query); |
||||
if (first.nextOffset == null) |
||||
return first.iterator(); |
||||
return new Iterator<Page>() { |
||||
Iterator<Page> current = first.iterator(); |
||||
Long nextOffset = first.nextOffset; |
||||
|
||||
@Override |
||||
public boolean hasNext() { |
||||
while (!current.hasNext() && nextOffset != null) { |
||||
System.out.println("Wow.. even more results than " + nextOffset); |
||||
Response<Page> nextPage = wikipedia.resumeSearch(query, nextOffset); |
||||
current = nextPage.iterator(); |
||||
nextOffset = nextPage.nextOffset; |
||||
} |
||||
return current.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public Page next() { |
||||
return current.next(); |
||||
} |
||||
|
||||
@Override |
||||
public void remove() { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Module(overrides = true, library = true, includes = GsonModule.class) |
||||
static class WikipediaModule { |
||||
|
||||
@Provides Logger.Level loggingLevel() { |
||||
return BASIC; |
||||
} |
||||
|
||||
@Provides Logger logger() { |
||||
return new ErrorLogger(); |
||||
} |
||||
|
||||
/** |
||||
* add to the set of Decoders one that handles {@code Response<Page>}. |
||||
*/ |
||||
@Provides(type = SET) Decoder pagesDecoder() { |
||||
return new ResponseDecoder<Page>() { |
||||
|
||||
@Override |
||||
protected String query() { |
||||
return "pages"; |
||||
} |
||||
|
||||
@Override |
||||
protected Page build(JsonReader reader) throws IOException { |
||||
Page page = new Page(); |
||||
while (reader.hasNext()) { |
||||
String key = reader.nextName(); |
||||
if (key.equals("pageid")) { |
||||
page.id = reader.nextLong(); |
||||
} else if (key.equals("title")) { |
||||
page.title = reader.nextString(); |
||||
} else { |
||||
reader.skipValue(); |
||||
} |
||||
} |
||||
return page; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
} |
@ -1,114 +0,0 @@
@@ -1,114 +0,0 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.InetAddress; |
||||
import java.net.Socket; |
||||
import java.security.SecureRandom; |
||||
import java.security.cert.X509Certificate; |
||||
|
||||
import javax.inject.Provider; |
||||
import javax.net.ssl.KeyManager; |
||||
import javax.net.ssl.SSLContext; |
||||
import javax.net.ssl.SSLSocket; |
||||
import javax.net.ssl.SSLSocketFactory; |
||||
import javax.net.ssl.TrustManager; |
||||
import javax.net.ssl.X509TrustManager; |
||||
|
||||
import static com.google.common.base.Throwables.propagate; |
||||
|
||||
/** |
||||
* used for ssl tests so that they can avoid having to read a keystore. |
||||
*/ |
||||
final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509TrustManager, KeyManager { |
||||
|
||||
public static SSLSocketFactory get() { |
||||
return Singleton.INSTANCE.get(); |
||||
} |
||||
|
||||
private final SSLSocketFactory delegate; |
||||
|
||||
private TrustingSSLSocketFactory() { |
||||
try { |
||||
SSLContext sc = SSLContext.getInstance("SSL"); |
||||
sc.init(new KeyManager[]{this}, new TrustManager[]{this}, new SecureRandom()); |
||||
this.delegate = sc.getSocketFactory(); |
||||
} catch (Exception e) { |
||||
throw propagate(e); |
||||
} |
||||
} |
||||
|
||||
@Override public String[] getDefaultCipherSuites() { |
||||
return ENABLED_CIPHER_SUITES; |
||||
} |
||||
|
||||
@Override public String[] getSupportedCipherSuites() { |
||||
return ENABLED_CIPHER_SUITES; |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(s, host, port, autoClose)); |
||||
} |
||||
|
||||
static Socket setEnabledCipherSuites(Socket socket) { |
||||
SSLSocket.class.cast(socket).setEnabledCipherSuites(ENABLED_CIPHER_SUITES); |
||||
return socket; |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(String host, int port) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(host, port)); |
||||
} |
||||
|
||||
@Override public Socket createSocket(InetAddress host, int port) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(host, port)); |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(host, port, localHost, localPort)); |
||||
} |
||||
|
||||
@Override |
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) |
||||
throws IOException { |
||||
return setEnabledCipherSuites(delegate.createSocket(address, port, localAddress, localPort)); |
||||
} |
||||
|
||||
public X509Certificate[] getAcceptedIssuers() { |
||||
return null; |
||||
} |
||||
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) { |
||||
} |
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) { |
||||
} |
||||
|
||||
private final static String[] ENABLED_CIPHER_SUITES = {"SSL_DH_anon_WITH_RC4_128_MD5"}; |
||||
|
||||
private static enum Singleton implements Provider<SSLSocketFactory> { |
||||
INSTANCE; |
||||
|
||||
private final SSLSocketFactory sslSocketFactory = new TrustingSSLSocketFactory(); |
||||
|
||||
@Override public SSLSocketFactory get() { |
||||
return sslSocketFactory; |
||||
} |
||||
} |
||||
} |
@ -1,76 +0,0 @@
@@ -1,76 +0,0 @@
|
||||
/* |
||||
* Copyright 2013 Netflix, Inc. |
||||
* |
||||
* 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 feign.jaxrs.examples; |
||||
|
||||
import javax.ws.rs.GET; |
||||
import javax.ws.rs.Path; |
||||
|
||||
import dagger.Module; |
||||
import dagger.Provides; |
||||
import feign.Feign; |
||||
import feign.Request; |
||||
import feign.RequestTemplate; |
||||
import feign.Target; |
||||
import feign.codec.Decoder; |
||||
import feign.codec.Decoders; |
||||
import feign.examples.AWSSignatureVersion4; |
||||
import feign.jaxrs.JAXRSModule; |
||||
|
||||
import static dagger.Provides.Type.SET; |
||||
|
||||
public class IAMExample { |
||||
|
||||
interface IAM { |
||||
@GET @Path("/?Action=GetUser&Version=2010-05-08") String arn(); |
||||
} |
||||
|
||||
public static void main(String... args) { |
||||
|
||||
IAM iam = Feign.create(new IAMTarget(args[0], args[1]), new IAMModule()); |
||||
System.out.println(iam.arn()); |
||||
} |
||||
|
||||
static class IAMTarget extends AWSSignatureVersion4 implements Target<IAM> { |
||||
|
||||
@Override public Class<IAM> type() { |
||||
return IAM.class; |
||||
} |
||||
|
||||
@Override public String name() { |
||||
return "iam"; |
||||
} |
||||
|
||||
@Override public String url() { |
||||
return "https://iam.amazonaws.com"; |
||||
} |
||||
|
||||
private IAMTarget(String accessKey, String secretKey) { |
||||
super(accessKey, secretKey); |
||||
} |
||||
|
||||
@Override public Request apply(RequestTemplate in) { |
||||
in.insert(0, url()); |
||||
return super.apply(in); |
||||
} |
||||
} |
||||
|
||||
@Module(overrides = true, library = true, includes = JAXRSModule.class) |
||||
static class IAMModule { |
||||
@Provides(type = SET) Decoder decoder() { |
||||
return Decoders.firstGroup("<Arn>([\\S&&[^<]]+)</Arn>"); |
||||
} |
||||
} |
||||
} |
@ -1 +1 @@
@@ -1 +1 @@
|
||||
version=4.0.0-SNAPSHOT |
||||
version=4.4.1-SNAPSHOT |
||||
|
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
# Feign JAXRS |
||||
This module overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec. |
||||
|
||||
## Limitations |
||||
While it may appear possible to reuse the same interface across client and server, bear in mind that JAX-RS resource |
||||
annotations were not designed to be processed by clients. Moreover, JAX-RS 2.0 has a different package hierarchy for |
||||
client invocation. Finally, JAX-RS is a large spec and attempts to implement it completely would be a project larger |
||||
than feign itself. In other words, this implementation is *best efforts* and concedes far from 100% compatibility with |
||||
server interface behavior. |
||||
|
||||
## Currently Supported Annotation Processing |
||||
Feign only supports processing java interfaces (not abstract or concrete classes). |
||||
|
||||
ISE is raised when any annotation's value is empty or null. Ex. `Path("")` raises an ISE. |
||||
|
||||
Here are a list of behaviors currently supported. |
||||
### Type Annotations |
||||
#### `@Path` |
||||
Appends the value to `Target.url()`. Can have tokens corresponding to `@PathParam` annotations. |
||||
### Method Annotations |
||||
#### `@HttpMethod` meta-annotation (present on `@GET`, `@POST`, etc.) |
||||
Sets the request method. |
||||
#### `@Path` |
||||
Appends the value to `Target.url()`. Can have tokens corresponding to `@PathParam` annotations. |
||||
#### `@Produces` |
||||
Adds the first value as the `Accept` header. |
||||
#### `@Consumes` |
||||
Adds the first value as the `Content-Type` header. |
||||
### Parameter Annotations |
||||
#### `@PathParam` |
||||
Links the value of the corresponding parameter to a template variable declared in the path. |
||||
#### `@QueryParam` |
||||
Links the value of the corresponding parameter to a query parameter. When invoked, null will skip the query param. |
||||
#### `@HeaderParam` |
||||
Links the value of the corresponding parameter to a header. |
||||
#### `@FormParam` |
||||
Links the value of the corresponding parameter to a key passed to `Encoder.Text<Map<String, Object>>.encode()`. |
@ -1,2 +1,6 @@
@@ -1,2 +1,6 @@
|
||||
rootProject.name='feign' |
||||
include 'feign-core', 'feign-gson', 'feign-jaxrs', 'feign-ribbon', 'examples:feign-example-cli' |
||||
include 'core', 'gson', 'jaxrs', 'ribbon', 'example-github', 'example-wikipedia' |
||||
|
||||
rootProject.children.each { childProject -> |
||||
childProject.name = 'feign-' + childProject.name |
||||
} |
||||
|
Loading…
Reference in new issue