diff --git a/app/build.gradle b/app/build.gradle index 1aa1fd0..4a5d4c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/main/java/com/shehabic/testsherlock/MainActivity.kt b/app/src/main/java/com/shehabic/testsherlock/MainActivity.kt index a53db06..6cb8466 100644 --- a/app/src/main/java/com/shehabic/testsherlock/MainActivity.kt +++ b/app/src/main/java/com/shehabic/testsherlock/MainActivity.kt @@ -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 @@ -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, @@ -35,7 +32,7 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) setSupportActionBar(toolbar) - fab.setOnClickListener { view -> + fab.setOnClickListener { makeRequest() } setupUI() @@ -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 diff --git a/build.gradle b/build.gradle index 5ed6696..3a2a875 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } diff --git a/gradle.properties b/gradle.properties index 328aa89..1bf571d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c213fd9..2b95822 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/settings.gradle b/settings.gradle index fa09d25..f2c07eb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':sherlock', ':sherlock-no-op' +include ':app', ':sherlock', ':sherlock-no-op', ":sherlock-plugin" diff --git a/sherlock-plugin/build.gradle b/sherlock-plugin/build.gradle new file mode 100644 index 0000000..84ce21f --- /dev/null +++ b/sherlock-plugin/build.gradle @@ -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' diff --git a/sherlock-plugin/gradle.properties b/sherlock-plugin/gradle.properties new file mode 100644 index 0000000..bec05b6 --- /dev/null +++ b/sherlock-plugin/gradle.properties @@ -0,0 +1,3 @@ +POM_NAME=sherlock-plugin +POM_ARTIFACT_ID=sherlock-plugin +POM_PACKAGING=jar diff --git a/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockClassWriter.java b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockClassWriter.java new file mode 100644 index 0000000..93097a1 --- /dev/null +++ b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockClassWriter.java @@ -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 - + // ()V - signature of method. () - method doesn't have parameters. V - method returns void + visitMethodInsn(Opcodes.INVOKESPECIAL, "com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor", "", "()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); + } + } +} diff --git a/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockExtension.java b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockExtension.java new file mode 100644 index 0000000..5570648 --- /dev/null +++ b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockExtension.java @@ -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; + } + +} diff --git a/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockInstrumentation.java b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockInstrumentation.java new file mode 100644 index 0000000..8ee9d54 --- /dev/null +++ b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockInstrumentation.java @@ -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 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 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); + } +} diff --git a/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockPlugin.java b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockPlugin.java new file mode 100644 index 0000000..10f10ba --- /dev/null +++ b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockPlugin.java @@ -0,0 +1,21 @@ +package com.shehabic.sherlock.plugin; + +import com.android.build.gradle.BaseExtension; +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +class SherlockPlugin implements Plugin { + @Override + public void apply(Project project) { + BaseExtension androidExt = (BaseExtension) project.getExtensions().getByName("android"); + + if (project.getPlugins().findPlugin("com.android.application") != null) { + project.getExtensions().create("SherlockExtension", SherlockExtension.class); + androidExt.registerTransform(new SherlockTransformer(project)); + } else if (project.getPlugins().findPlugin("com.android.library") != null) { + throw new RuntimeException("Sherlock cannot be applied to library project", null); + } else { + throw new RuntimeException("Need android application plugin to be applied first", null); + } + } +} diff --git a/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockTransformer.java b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockTransformer.java new file mode 100644 index 0000000..ca12e9c --- /dev/null +++ b/sherlock-plugin/src/main/java/com/shehabic/sherlock/plugin/SherlockTransformer.java @@ -0,0 +1,236 @@ +package com.shehabic.sherlock.plugin; + +import com.android.build.api.transform.DirectoryInput; +import com.android.build.api.transform.Format; +import com.android.build.api.transform.JarInput; +import com.android.build.api.transform.QualifiedContent; +import com.android.build.api.transform.QualifiedContent.Scope; +import com.android.build.api.transform.Status; +import com.android.build.api.transform.Transform; +import com.android.build.api.transform.TransformInput; +import com.android.build.api.transform.TransformInvocation; +import com.android.build.api.transform.TransformOutputProvider; +import com.android.build.gradle.AppExtension; +import com.android.build.gradle.internal.pipeline.TransformManager; +import com.google.common.collect.Lists; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.gradle.api.Project; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +class SherlockTransformer extends Transform { + + private final Project project; + private final Set typeClasses; + private final Set scopes; + private AppExtension appExt; + private boolean sherlockEnabled = true; + private SherlockInstrumentation instrumentation; + + SherlockTransformer(final Project project) { + this.project = project; + this.typeClasses = TransformManager.CONTENT_CLASS; + this.scopes = TransformManager.SCOPE_FULL_PROJECT; + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Set getInputTypes() { + return typeClasses; + } + + @Override + public Set getScopes() { + return scopes; + } + + @Override + public boolean isIncremental() { + return false; + } + + public void transform(TransformInvocation invocation) throws IOException { + sherlockEnabled = project.getExtensions().getByType(SherlockExtension.class).enabled; + if (!sherlockEnabled) return; + Collection inputs = invocation.getInputs(); + Collection referencedInputs = invocation.getReferencedInputs(); + TransformOutputProvider outputProvider = invocation.getOutputProvider(); + boolean incremental = invocation.isIncremental(); + this.appExt = (AppExtension)this.project.getExtensions().findByName("android"); + List runtimeCP = this.buildRuntimeClasspath(inputs, referencedInputs); + try (URLClassLoader cl = new URLClassLoader(runtimeCP.toArray(new URL[0]), null)) { + if (!incremental) outputProvider.deleteAll(); + this.instrumentation = new SherlockInstrumentation(cl); + for (TransformInput input: inputs) { + this.transformDirectoryInputs(input, incremental, outputProvider); + this.transformJarInputs(input, incremental, outputProvider); + } + } catch (Throwable e) { + System.out.println("[Sherlock Plugin Error]: " + e.getLocalizedMessage()); + e.printStackTrace(); + throw e; + } + } + + private void transformJarInputs(TransformInput ti, boolean incremental, TransformOutputProvider outputProvider) throws IOException { + Iterator jars = ti.getJarInputs().iterator(); + + while(true) { + File jar; + File outDir; + label233: + do { + while(true) { + while(jars.hasNext()) { + JarInput jarInput = (JarInput) jars.next(); + jar = jarInput.getFile(); + String jarName = jar.getName(); + int lastIndex = jarName.lastIndexOf(46); + String uniqueName; + String md5; + String baseName; + if (lastIndex != -1) { + md5 = DigestUtils.md5Hex(jar.getPath()); + baseName = jarName.substring(0, lastIndex); + uniqueName = md5 + "-" + baseName; + } else { + md5 = DigestUtils.md5Hex(jar.getPath()); + uniqueName = md5 + "-" + jarName; + } + + outDir = outputProvider.getContentLocation(uniqueName, jarInput.getContentTypes(), jarInput + .getScopes(), Format.DIRECTORY); + boolean doXForm = !incremental || jarInput.getStatus() == Status.ADDED || jarInput + .getStatus() == Status.CHANGED; + if (doXForm) { + if (this.sherlockEnabled) { + continue label233; + } + + File outJar = outputProvider.getContentLocation(uniqueName, jarInput.getContentTypes(), jarInput + .getScopes(), Format.JAR); + if (!outJar.getParentFile().mkdirs() && !outJar.getParentFile().isDirectory()) { + String outputDir = String.valueOf(outDir); + throw new IOException("Couldn't create transformExt output " + outputDir); + } + + FileInputStream fis = new FileInputStream(jarInput.getFile()); + + try { + FileOutputStream fos = new FileOutputStream(outJar); + + try { + IOUtils.copy(fis, fos); + } catch (Throwable t) { + t.printStackTrace(); + throw t; + } finally { + fos.close(); + } + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } finally { + fis.close(); + } + } else if (jarInput.getStatus() == Status.REMOVED) { + FileUtils.deleteQuietly(outDir); + } else { + String filename = String.valueOf(jarInput.getFile()); +// System.out.println("[Sherlock Plugin] --- Skipping ---> " + filename); + } + } + + return; + } + } while(!outDir.mkdirs() && !outDir.isDirectory()); + + this.instrumentation.instrumentClassesInJar(jar, outDir); + } + } + + private void transformDirectoryInputs(TransformInput ti, boolean incremental, TransformOutputProvider outputProvider) throws IOException { + Iterator dirIterators = ti.getDirectoryInputs().iterator(); + + while(true) { + DirectoryInput di; + File outDir; + Map changed; + + do { + if (!dirIterators.hasNext()) return; + + di = (DirectoryInput) dirIterators.next(); + String uniqueName = DigestUtils.md5Hex(di.getFile().getPath()); + outDir = outputProvider.getContentLocation(uniqueName, di.getContentTypes(), di.getScopes(), Format.DIRECTORY); + changed = null; + if (incremental) { + changed = di.getChangedFiles(); + } + } while(!outDir.mkdirs() && !outDir.isDirectory()); + + if (this.sherlockEnabled) { + instrumentation.instrumentClassesInDir(di.getFile(), 0, String.valueOf(outDir.toString()).concat("/"), changed); + } else { + FileUtils.deleteDirectory(outDir); + FileUtils.moveDirectory(di.getFile(), outDir); + } + } + } + + private List buildRuntimeClasspath(Collection inputs, Collection referencedInputs) { + ArrayList cp = new ArrayList<>(this.appExt.getBootClasspath()); + + for (Object o : Arrays.asList(inputs, referencedInputs)) { + Collection tis = (Collection) o; + + for (TransformInput ti : tis) { + List> allQC = Arrays.asList( + ti.getDirectoryInputs(), + ti.getJarInputs() + ); + + for (Object anAllQC : allQC) { + Collection qcs = (Collection) anAllQC; + Iterator iterator = qcs.iterator(); + + while (iterator.hasNext()) { + QualifiedContent qc = (QualifiedContent) iterator.next(); + cp.add(qc.getFile()); + } + } + } + } + + return Lists.transform(cp, file -> { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + System.out.println("Sherlock Plugin Error: " + e.getLocalizedMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + } + }); + } + +} diff --git a/sherlock-plugin/src/main/resources/META-INF/gradle-plugins/com.shehabic.sherlock.plugin.properties b/sherlock-plugin/src/main/resources/META-INF/gradle-plugins/com.shehabic.sherlock.plugin.properties new file mode 100644 index 0000000..78e37da --- /dev/null +++ b/sherlock-plugin/src/main/resources/META-INF/gradle-plugins/com.shehabic.sherlock.plugin.properties @@ -0,0 +1 @@ +implementation-class=com.shehabic.sherlock.plugin.SherlockPlugin diff --git a/sherlock/src/main/java/com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor.kt b/sherlock/src/main/java/com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor.kt index eeab479..162f217 100644 --- a/sherlock/src/main/java/com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor.kt +++ b/sherlock/src/main/java/com/shehabic/sherlock/interceptors/SherlockOkHttpInterceptor.kt @@ -2,31 +2,102 @@ package com.shehabic.sherlock.interceptors import com.shehabic.sherlock.NetworkSherlock import com.shehabic.sherlock.db.NetworkRequests -import okhttp3.Headers -import okhttp3.Interceptor -import okhttp3.Response +import okhttp3.* import okio.Buffer import java.io.EOFException import java.io.IOException import java.nio.charset.Charset - class SherlockOkHttpInterceptor : Interceptor { + companion object { + @JvmStatic + fun appendResponseData( + response: Response, + request: NetworkRequests + ) { + val responseBody = response.body() + val contentLength = responseBody!!.contentLength() + if (this.bodyEncoded(response.headers())) { + request.responseBody = "" + } else { + val source = responseBody.source() + source.request(Long.MAX_VALUE) + val buffer = source.buffer() + var charset: Charset? = Charset.defaultCharset() + val contentType = responseBody.contentType() + if (contentType != null) { + charset = contentType.charset(Charset.defaultCharset()) + } + request.responseBody = "" + if (!isPlaintext(buffer)) { + request.responseBody = "" + } else if (contentLength != 0L) { + request.responseBody = buffer.clone().readString(charset!!) + } + } + request.statusCode = response.code() + request.responseHeaders = response.headers().toString() + request.responseLength = response.body()?.contentLength() ?: 0L + request.requestStartTime = response.sentRequestAtMillis() + request.requestHeaders = response.networkResponse()?.request()?.headers().toString() + request.responseTime = response.receivedResponseAtMillis() - request.requestStartTime + request.responseMessage = response.message()?.toString() + request.responseContentType = response.body()?.contentType()?.toString() + request.isRedirect = response.isRedirect + request.protocol = response.protocol().toString() + } + + @JvmStatic + private fun createNetworkRequest(srcRequest: Request): NetworkRequests { + val request = NetworkRequests(0, NetworkSherlock.getInstance().getSessionId()) + request.method = srcRequest.method() + request.requestUrl = srcRequest.url().toString() + request.requestHeaders = srcRequest.headers().toString() + request.requestStartTime = System.currentTimeMillis() + try { + val buffer = Buffer() + srcRequest.body()?.writeTo(buffer) + request.requestBody = buffer.readUtf8() + } catch (e: Exception) { + request.requestBody = "" + } + request.requestContentType = srcRequest.body()?.contentType()?.toString() + return request + } + + @JvmStatic + private fun isPlaintext(buffer: Buffer): Boolean { + try { + val prefix = Buffer() + val byteCount = if (buffer.size() < 64L) buffer.size() else 64L + buffer.copyTo(prefix, 0L, byteCount) + + var i = 0 + while (i < 16 && !prefix.exhausted()) { + val codePoint = prefix.readUtf8CodePoint() + if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { + return false + } + ++i + } + + return true + } catch (var6: EOFException) { + return false + } + + } + + @JvmStatic + private fun bodyEncoded(headers: Headers): Boolean { + val contentEncoding = headers.get("Content-Encoding") + return contentEncoding != null && !contentEncoding.equals("identity", ignoreCase = true) + } + } + override fun intercept(chain: Interceptor.Chain?): Response { val srcRequest = chain?.request()!! - val request = NetworkRequests(0, NetworkSherlock.getInstance().getSessionId()) - request.method = srcRequest.method() - request.requestUrl = srcRequest.url().toString() - request.requestHeaders = srcRequest.headers().toString() - request.requestStartTime = System.currentTimeMillis() - try { - val buffer = Buffer() - srcRequest.body()?.writeTo(buffer) - request.requestBody = buffer.readUtf8() - } catch (e: Exception) { - request.requestBody = "" - } - request.requestContentType = srcRequest.body()?.contentType()?.toString() + val request = createNetworkRequest(srcRequest) NetworkSherlock.getInstance().startRequest() val response: Response? try { @@ -38,67 +109,11 @@ class SherlockOkHttpInterceptor : Interceptor { NetworkSherlock.getInstance().endRequest() throw e } - val responseBody = response.body() - val contentLength = responseBody!!.contentLength() - if (this.bodyEncoded(response.headers())) { - request.responseBody = "" - } else { - val source = responseBody.source() - source.request(Long.MAX_VALUE) - val buffer = source.buffer() - var charset: Charset? = Charset.defaultCharset() - val contentType = responseBody.contentType() - if (contentType != null) { - charset = contentType.charset(Charset.defaultCharset()) - } - request.responseBody = "" - if (!isPlaintext(buffer)) { - request.responseBody = "" - } else if (contentLength != 0L) { - request.responseBody = buffer.clone().readString(charset!!) - } - } - request.statusCode = response.code() - request.responseHeaders = response.headers().toString() - request.responseLength = response.body()?.contentLength() ?: 0L - request.requestStartTime = response.sentRequestAtMillis() - request.requestHeaders = response.networkResponse()?.request()?.headers().toString() - request.responseTime = response.receivedResponseAtMillis() - request.requestStartTime - request.responseMessage = response.message()?.toString() - request.responseContentType = response.body()?.contentType()?.toString() - request.isRedirect = response.isRedirect - request.protocol = response.protocol().toString() + appendResponseData(response, request) NetworkSherlock.getInstance().addRequest(request) NetworkSherlock.getInstance().endRequest() return response } - - private fun isPlaintext(buffer: Buffer): Boolean { - try { - val prefix = Buffer() - val byteCount = if (buffer.size() < 64L) buffer.size() else 64L - buffer.copyTo(prefix, 0L, byteCount) - - var i = 0 - while (i < 16 && !prefix.exhausted()) { - val codePoint = prefix.readUtf8CodePoint() - if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { - return false - } - ++i - } - - return true - } catch (var6: EOFException) { - return false - } - - } - - private fun bodyEncoded(headers: Headers): Boolean { - val contentEncoding = headers.get("Content-Encoding") - return contentEncoding != null && !contentEncoding.equals("identity", ignoreCase = true) - } -} \ No newline at end of file +} diff --git a/sherlock/src/main/java/com/shehabic/sherlock/ui/NetworkSherlockAnchor.kt b/sherlock/src/main/java/com/shehabic/sherlock/ui/NetworkSherlockAnchor.kt index b00aa65..55715b8 100644 --- a/sherlock/src/main/java/com/shehabic/sherlock/ui/NetworkSherlockAnchor.kt +++ b/sherlock/src/main/java/com/shehabic/sherlock/ui/NetworkSherlockAnchor.kt @@ -130,4 +130,4 @@ class NetworkSherlockAnchor { } } } -} \ No newline at end of file +}