Skip to content

Creating plugin to intercept all OkHttp connections without adding interceptor #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

apply plugin: 'sherlock-plugin'

android {
configurations {
Expand Down
8 changes: 3 additions & 5 deletions app/src/main/java/com/shehabic/testsherlock/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import android.support.v7.app.AppCompatActivity
import android.widget.ArrayAdapter
import android.widget.Toast
import com.shehabic.sherlock.NetworkSherlock
import com.shehabic.sherlock.interceptors.SherlockOkHttpInterceptor
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
import okhttp3.MediaType
Expand All @@ -17,8 +16,6 @@ import okhttp3.RequestBody

class MainActivity : AppCompatActivity() {

val endPoint = "https://api.github.com/users/shehabic"

data class DynamicRequest(
val url: String,
val body: String,
Expand All @@ -35,7 +32,7 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)

fab.setOnClickListener { view ->
fab.setOnClickListener {
makeRequest()
}
setupUI()
Expand Down Expand Up @@ -77,7 +74,8 @@ class MainActivity : AppCompatActivity() {
private fun networkCall(request: DynamicRequest) {
val client: OkHttpClient = OkHttpClient
.Builder()
.addInterceptor(SherlockOkHttpInterceptor())
// No need for this when using the plugin
// .addInterceptor(SherlockOkHttpInterceptor())
.build()
val body: RequestBody? = when(request.method) {
"GET", "HEAD", "OPTION" -> null
Expand Down
12 changes: 8 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.2.71'
ext.kotlin_version = '1.3.11'
ext.architecture_components_version = '1.1.1'
ext.support_libraries_version = '27.1.1'
ext.okhttp_version = '3.11.0'
ext.VERSION_NAME = "0.11.0"
ext.VERSION_NAME = VERSION_CODE

repositories {
google()
jcenter()
mavenLocal()
maven { url 'https://jitpack.io' }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath "com.gradle.publish:plugin-publish-plugin:0.9.9"
classpath 'com.github.shehabic.sherlock:sherlock-plugin:1.1.7'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx4096M
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

VERSION_CODE=0.9.2
VERSION_CODE=1.1.6

POM_DEVELOPER_ID=shehabic
POM_DEVELOPER_NAME=Mohamed Shehab Osman
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1 @@
include ':app', ':sherlock', ':sherlock-no-op'
include ':app', ':sherlock', ':sherlock-no-op', ":sherlock-plugin"
58 changes: 58 additions & 0 deletions sherlock-plugin/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
apply plugin: 'java-gradle-plugin'
apply plugin: 'maven-publish'
apply plugin: 'com.gradle.plugin-publish'

repositories {
google()
mavenCentral()
jcenter()
}

dependencies {
ext.okhttp_version = '3.11.0'
implementation gradleApi()
implementation 'com.squareup:javapoet:1.11.1'
implementation 'com.github.javaparser:javaparser-core:3.9.1'
implementation 'commons-io:commons-io:2.6'
implementation 'com.android.tools.build:gradle:3.3.0'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
}

gradlePlugin {
plugins {
sherlockPlugin {
id = 'sherlock-plugin'
implementationClass = 'com.shehabic.sherlock.plugin.SherlockPlugin'
}
}
}


task sourcesJar(type: Jar) {
from sourceSets.main.allJava
classifier = 'sources'
}

task javadocJar(type: Jar) {
from javadoc
classifier = 'javadoc'
}


//apply from: rootProject.file('gradle/publish.gradle')
publishing {
publications {
maven(MavenPublication) {
groupId 'com.github.shehabic'
artifactId 'sherlock-plugin'
version VERSION_NAME
from components.java
}
}
repositories {
maven {
url "~/.m2/repository"
}
}
}
apply plugin: 'com.github.dcendents.android-maven'
3 changes: 3 additions & 0 deletions sherlock-plugin/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_NAME=sherlock-plugin
POM_ARTIFACT_ID=sherlock-plugin
POM_PACKAGING=jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.shehabic.sherlock.plugin;

import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;

/**
* @author shehabic
*/
class SherlockClassWriter {

byte[] instrument(byte[] contents) {
ClassReader reader = new ClassReader(contents);
ClassWriter writer = new ClassWriter(reader, COMPUTE_FRAMES);
reader.accept(new SherlockClassVisitor(writer), new Attribute[]{}, SKIP_FRAMES);
return writer.toByteArray();
}

public class SherlockClassVisitor extends ClassVisitor {
SherlockClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}

@Override
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions
) {
if (name.equals("build")) {
System.out.println("Will do the builder here ... ");
return new SherlockMethodVisitor(super.visitMethod(
access,
name,
desc,
signature,
exceptions
));
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
}


private static class SherlockMethodVisitor extends MethodVisitor {
SherlockMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}

// This method will be called before almost all instructions
@Override
public void visitCode() {
// Puts 'this' on top of the stack. If your method is static just delete it
visitVarInsn(Opcodes.ALOAD, 0);
// Takes instance of class "the/full/name/of/your/Class" from top of the stack and put value of field interceptors
// "Ljava/util/List;" is just internal name of java.util.List
visitFieldInsn(Opcodes.GETFIELD, "okhttp3/OkHttpClient$Builder", "networkInterceptors", "Ljava/util/List;");
// Before we call add method of list we have to put target value on top of the stack
visitTypeInsn(Opcodes.NEW, "com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor");
visitInsn(Opcodes.DUP);
// We have to call classes constructor
// Internal name of constructor - <init>
// ()V - signature of method. () - method doesn't have parameters. V - method returns void
visitMethodInsn(Opcodes.INVOKESPECIAL, "com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor", "<init>", "()V", false);
// So on top of the stack we have initialized instance of com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor
// Now we can put it into list
visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.shehabic.sherlock.plugin;

public class SherlockExtension {

public boolean enabled = true;

public SherlockExtension() {
}

public void setInstrumentationEnabled(boolean val) {
this.enabled = val;
}

public boolean isInstrumentationEnabled() {
return this.enabled;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.shehabic.sherlock.plugin;

import com.android.build.api.transform.Status;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;

import org.apache.commons.io.FileUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

class SherlockInstrumentation {

private static final String CLASS_TO_INSTRUMENT = "okhttp3/OkHttpClient$Builder";

SherlockInstrumentation(ClassLoader cl) {
}

void instrumentClassesInDir(
final File dir,
int depth,
String outDir,
Map<File, Status> changed
) throws IOException {
String[] names = dir.list();
if (names == null) {
throw new IOException("File list was unexpectedly null");
} else {
for (String dirName : names) {
if (depth == 0 && dirName.equals("android")) {
FileUtils.copyDirectory(dir, new File(outDir));
} else {
File file = new File(dir, dirName);
if (file.isDirectory()) {
++depth;
String fileName = file.getName();
this.instrumentClassesInDir(file, depth, outDir + fileName + "/", changed);
} else if (dirName.endsWith(".class")) {
String outputDir = String.valueOf(outDir);
String fileName = dirName;
if (fileName.length() != 0) {
outputDir = outputDir.concat(fileName);
} else {
fileName = outputDir;
outputDir = fileName;
}

this.instrumentClassFile(file, outputDir, changed);
}
}
}

}
}

private void instrumentClassFile(final File f, String outDir, Map<File, Status> changed) throws IOException {
File outFile = new File(outDir);
outFile.getParentFile().mkdirs();
outFile.createNewFile();
if (changed != null) {
Status status = changed.get(f);
if (status == null || status == Status.NOTCHANGED || status == Status.REMOVED) {
Files.copy(f, outFile);
return;
}
}

InputStream is = new BufferedInputStream(new FileInputStream(f));
// byte[] fileBytes = ByteStreams.toByteArray(is);
is.close();

Files.copy(f, outFile);
// try {
// byte[] out = this.instrument(fileBytes);
// FileOutputStream fos = new FileOutputStream(outFile);
// fos.write(out);
// fos.close();
// } catch (Exception e) {
// System.out.print("[Sherlock Plugin Error]: " + e.getLocalizedMessage());
// e.printStackTrace();
// Files.copy(f, outFile);
// }

}

void instrumentClassesInJar(File inJarFile, File outDir) throws IOException {
JarInputStream jis = new JarInputStream(new BufferedInputStream(new FileInputStream(inJarFile)));

JarEntry inEntry;
while ((inEntry = jis.getNextJarEntry()) != null) {
String name = inEntry.getName();
byte[] entryBytes = ByteStreams.toByteArray(jis);
jis.closeEntry();
if (name.endsWith(".class") && this.isInstrumentable(name)) {
try {
entryBytes = this.instrument(entryBytes);
} catch (Exception e) {
System.out.println("[Sherlock Plugin Error]: " + e.getLocalizedMessage());
e.printStackTrace();
}
}

if (!inEntry.isDirectory()) {
String var8 = String.valueOf(outDir);
File outFile = new File(var8 + "/" + name);
outFile.getParentFile().mkdirs();
outFile.createNewFile();
FileOutputStream fos = new FileOutputStream(outFile);
fos.write(entryBytes);
fos.close();
}
}

jis.close();
}

private boolean isInstrumentable(String name) {
return name.startsWith(CLASS_TO_INSTRUMENT);
}

private byte[] instrument(final byte[] in) {
SherlockClassWriter cw = new SherlockClassWriter();

return cw.instrument(in);
}
}
Loading