kandi background
Explore Kits

retrofit | A typesafe HTTP client for Android and the JVM | HTTP Client library

 by   square Java Version: Current License: Apache-2.0

 by   square Java Version: Current License: Apache-2.0

Download this library from

kandi X-RAY | retrofit Summary

retrofit is a Java library typically used in Utilities, HTTP Client applications. retrofit has no bugs, it has build file available, it has a Permissive License and it has high support. However retrofit has 2 vulnerabilities. You can download it from GitHub, Maven.
A type-safe HTTP client for Android and Java. For more information please see [the website][1].
Support
Support
Quality
Quality
Security
Security
License
License
Reuse
Reuse

kandi-support Support

  • retrofit has a highly active ecosystem.
  • It has 39764 star(s) with 7090 fork(s). There are 1607 watchers for this library.
  • It had no major release in the last 12 months.
  • There are 135 open issues and 2427 have been closed. On average issues are closed in 16 days. There are 29 open pull requests and 0 closed requests.
  • It has a positive sentiment in the developer community.
  • The latest version of retrofit is current.
retrofit Support
Best in #HTTP Client
Average in #HTTP Client
retrofit Support
Best in #HTTP Client
Average in #HTTP Client

quality kandi Quality

  • retrofit has 0 bugs and 0 code smells.
retrofit Quality
Best in #HTTP Client
Average in #HTTP Client
retrofit Quality
Best in #HTTP Client
Average in #HTTP Client

securitySecurity

  • retrofit has 2 vulnerability issues reported (1 critical, 1 high, 0 medium, 0 low).
  • retrofit code analysis shows 0 unresolved vulnerabilities.
  • There are 0 security hotspots that need review.
retrofit Security
Best in #HTTP Client
Average in #HTTP Client
retrofit Security
Best in #HTTP Client
Average in #HTTP Client

license License

  • retrofit is licensed under the Apache-2.0 License. This license is Permissive.
  • Permissive licenses have the least restrictions, and you can use them in most projects.
retrofit License
Best in #HTTP Client
Average in #HTTP Client
retrofit License
Best in #HTTP Client
Average in #HTTP Client

buildReuse

  • retrofit releases are not available. You will need to build from source code and install.
  • Deployable package is available in Maven.
  • Build file is available. You can build the component from source.
  • Installation instructions, examples and code snippets are available.
  • retrofit saves you 5021 person hours of effort in developing the same functionality from scratch.
  • It has 27997 lines of code, 2400 functions and 299 files.
  • It has low code complexity. Code complexity directly impacts maintainability of the code.
retrofit Reuse
Best in #HTTP Client
Average in #HTTP Client
retrofit Reuse
Best in #HTTP Client
Average in #HTTP Client
Top functions reviewed by kandi - BETA

kandi has reviewed retrofit and discovered the below as its top functions. This is intended to give you an instant insight into retrofit implemented functionality, and help decide if they suit your requirements.

  • Parses annotations .
  • Resolves the given type using the given context .
  • Enqueues the Callback .
  • Gets the call adapter for the return type .
  • Crawl the specified URL .
  • Delivers a response .
  • Gets the next request body converter .
  • Submits a call to the server .
  • Parse the service method adapter info .
  • Adapts a call to the Observable .

retrofit Key Features

A type-safe HTTP client for Android and the JVM

License

copy iconCopydownload iconDownload
Copyright 2013 Square, Inc.

Retrofit OkHttp - "unexpected end of stream"

copy iconCopydownload iconDownload
interface ApiServiceInterface {

    companion object Factory{

        fun create(): ApiServiceInterface {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY
            
            val stringInterceptor = Interceptor { chain: Interceptor.Chain ->
                val request = chain.request()
                val response = chain.proceed(request)
                val source = response.body()?.source()
                source?.request(Long.MAX_VALUE)
                val buffer = source?.buffer()
                var responseString = buffer?.clone()?.readString(Charset.forName("UTF-8"))
                if (responseString != null && responseString.length > 2) {
                    val lastTwo = responseString.takeLast(2)
                    if (lastTwo != "}}") {
                        val lastOne = responseString.takeLast(1)
                        responseString = if (lastOne != "}") {
                            "$responseString}}"
                        } else {
                            "$responseString}"
                        }
                    }
                }
                val contentType = response.body()?.contentType()
                val body = ResponseBody.create(contentType, responseString ?: "")
                return@Interceptor response.newBuilder().body(body).build()
            }

            val client = OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30,TimeUnit.SECONDS)
                .addInterceptor(interceptor)
                .addInterceptor(stringInterceptor)
                .retryOnConnectionFailure(true)
                .connectionPool(ConnectionPool(0, 5, TimeUnit.MINUTES))
                .protocols(listOf(Protocol.HTTP_1_1))
                .build()
            val gson = GsonBuilder().create()
            val retrofit = Retrofit.Builder()
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addConverterFactory(ScalarsConverterFactory.create())
                .baseUrl("http://3.124.6.203:5000")
                .client(client)
                .build()
            return retrofit.create(ApiServiceInterface::class.java)
        }
    }


    @Headers("Content-type: application/json", "Connection: close", "Accept-Encoding: identity")
    @POST("/")
    fun requestAsync(@Body data: JsonObject): Deferred<Response>
}

android datastore-preferences: Property delegate must have a 'getValue(Context, KProperty&lt;*&gt;)' method

copy iconCopydownload iconDownload
import java.util.prefs.Preferences
import androidx.datastore.preferences.core.Preferences
val Context.dataStore by preferencesDataStore(name = "settings")
-----------------------
import java.util.prefs.Preferences
import androidx.datastore.preferences.core.Preferences
val Context.dataStore by preferencesDataStore(name = "settings")
-----------------------
import java.util.prefs.Preferences
import androidx.datastore.preferences.core.Preferences
val Context.dataStore by preferencesDataStore(name = "settings")

android:exported needs to be explicitly specified for &lt;activity&gt;. Apps targeting Android 12 and higher are required to specify

copy iconCopydownload iconDownload
<receiver android:name="<name_of_the_entry>"
                android:exported="false or true"
                tools:node="merge" />
-----------------------
     <activity
                android:name="<activity which is giving error>"
                android:exported="true"
                tools:node="merge" />
-----------------------
<activity android:name="name_of_the_activity_inside_library>"
    android:exported="false|true"
    tools:node="merge" />
-----------------------
debugImplementation "androidx.fragment:fragment-testing:<version>"
androidTestImplementation "androidx.fragment:fragment-testing:<version>"
-----------------------
debugImplementation "androidx.fragment:fragment-testing:<version>"
androidTestImplementation "androidx.fragment:fragment-testing:<version>"
-----------------------
 android:exported="true"
-----------------------
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.4"
-----------------------
> java.util.concurrent.ExecutionException: com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: Failed parse during installPackageLI: /data/app/xxxxx.tmp/base.apk (at Binary XML file line #129): YOUR.FULLY.QUALIFIED.NAME.FAILING.ACTIVITY: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present
-----------------------
<activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity"
android:exported="true"
tools:node="merge"/>

<activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
android:exported="true"
tools:node="merge"/>

<activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyFloatingActivity"
android:exported="true"
tools:node="merge"/>
-----------------------
 <activity
        android:name="com.test.activity.SplashActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:noHistory="true"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true">
   <receiver
        android:name="com.test.receiver.ShareReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.fcm.android.action.broadcast" />
        </intent-filter>
    </receiver>

   <service
        android:name="com.google.android.gms.tagmanager.InstallReferrerService"
        android:exported="true" />
testImplementation 'junit:junit:4.13.2' 
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.razorpay:checkout:1.6.15'
-----------------------
 <activity
        android:name="com.test.activity.SplashActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:noHistory="true"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true">
   <receiver
        android:name="com.test.receiver.ShareReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.fcm.android.action.broadcast" />
        </intent-filter>
    </receiver>

   <service
        android:name="com.google.android.gms.tagmanager.InstallReferrerService"
        android:exported="true" />
testImplementation 'junit:junit:4.13.2' 
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.razorpay:checkout:1.6.15'
-----------------------
 <activity
        android:name="com.test.activity.SplashActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:noHistory="true"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true">
   <receiver
        android:name="com.test.receiver.ShareReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.fcm.android.action.broadcast" />
        </intent-filter>
    </receiver>

   <service
        android:name="com.google.android.gms.tagmanager.InstallReferrerService"
        android:exported="true" />
testImplementation 'junit:junit:4.13.2' 
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.razorpay:checkout:1.6.15'
-----------------------
"com.google.dagger:hilt-android:2.38.1"
"com.google.dagger:hilt-android-gradle-plugin:2.38.1"
"com.google.dagger:hilt-android-compiler:2.38.1"
"com.google.dagger:hilt-android-testing:2.38.1"
"com.google.dagger:hilt-android:2.40.5"
"com.google.dagger:hilt-android-gradle-plugin:2.40.5"
"com.google.dagger:hilt-android-compiler:2.40.5"
"com.google.dagger:hilt-android-testing:2.40.5" 
-----------------------
"com.google.dagger:hilt-android:2.38.1"
"com.google.dagger:hilt-android-gradle-plugin:2.38.1"
"com.google.dagger:hilt-android-compiler:2.38.1"
"com.google.dagger:hilt-android-testing:2.38.1"
"com.google.dagger:hilt-android:2.40.5"
"com.google.dagger:hilt-android-gradle-plugin:2.40.5"
"com.google.dagger:hilt-android-compiler:2.40.5"
"com.google.dagger:hilt-android-testing:2.40.5" 
-----------------------
<receiver
    android:name="com.razorpay.RzpTokenReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="rzp.device_token.share" />
    </intent-filter>
</receiver>

<activity
    android:name="com.razorpay.CheckoutActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
    android:exported="true"
    android:theme="@style/CheckoutTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <data
            android:host="rzp.io"
            android:scheme="io.rzp" />
    </intent-filter>
</activity>
-----------------------
android:exported="true" <!-- or false as required -->
-----------------------
<activity
     android:name=".MainActivity"
     android:exported="true" <** add this line on AndroidManifest.xml**
     android:launchMode="singleTop"
     android:theme="@style/LaunchTheme"
 </activity>

One or more issues found when checking AAR metadata values:

copy iconCopydownload iconDownload
   compileSdk 31

    defaultConfig {
        applicationId "com.example.lifecycle"
        minSdk 21
        targetSdk 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

Android Build Error: &quot;lStar not found...&quot;

copy iconCopydownload iconDownload
compileSdk = 31

classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10'
-----------------------
implementation 'androidx.appcompat:appcompat:1.3.0'
-----------------------
configurations.all {
        resolutionStrategy.force 'androidx.appcompat:appcompat:1.3.0'
    }

Could not resolve com.google.guava:guava:30.1-jre - Gradle project sync failed. Basic functionality will not work properly - in kotlin project

copy iconCopydownload iconDownload
    repositories {
        mavenCentral()
        google()
    }

-----------------------
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenCentral()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Hilt circular dependency

copy iconCopydownload iconDownload
class AccessTokenRefreshDataSourceImpl @Inject constructor(
  private val authenticationServiceProvider:
    Provider<AuthenticationService>,
  private val userPrefsDataSource: UserPrefsDataSource,
) : AccessTokenRefreshDataSource {
@Singleton
@Provides
fun providesAccessTokenRefreshDataSource(
    userPrefsDataSource: UserPrefsDataSource,
    authenticationServiceProvider: Provider<AuthenticationService>,  // here
): AccessTokenRefreshDataSource = AccessTokenRefreshDataSourceImpl(
    authenticationServiceProvider /* and here */, userPrefsDataSource
)
-----------------------
class AccessTokenRefreshDataSourceImpl @Inject constructor(
  private val authenticationServiceProvider:
    Provider<AuthenticationService>,
  private val userPrefsDataSource: UserPrefsDataSource,
) : AccessTokenRefreshDataSource {
@Singleton
@Provides
fun providesAccessTokenRefreshDataSource(
    userPrefsDataSource: UserPrefsDataSource,
    authenticationServiceProvider: Provider<AuthenticationService>,  // here
): AccessTokenRefreshDataSource = AccessTokenRefreshDataSourceImpl(
    authenticationServiceProvider /* and here */, userPrefsDataSource
)

Data Class with Nullable Parameter in Kotlin 1.5.0 &amp; 1.6.0 Throwing BackendException

copy iconCopydownload iconDownload
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    val kotlinVersion = "1.6.0"
    kotlin("jvm") version kotlinVersion
}

repositories {
    mavenCentral()
}

Update @url path with different path parameters

copy iconCopydownload iconDownload
@Url fullPath: String = "https://myBaseUrl/v2/products/" +storeCode+ "/search",
-----------------------
@GET("{baseUrl}products/{storeCode}/search")
fun searchProductV2(
    @Path("baseUrl") baseUrl: String = "https://myBaseUrl/v2/",
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
@GET(Constants.API_BASE_URL_V2 + "products/{storeCode}/search")
fun searchProductV2(
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
@GET(Constants.API_VERSION_V2 + "/products/{storeCode}/search")
fun searchProductV2(
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
-----------------------
@GET("{baseUrl}products/{storeCode}/search")
fun searchProductV2(
    @Path("baseUrl") baseUrl: String = "https://myBaseUrl/v2/",
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
@GET(Constants.API_BASE_URL_V2 + "products/{storeCode}/search")
fun searchProductV2(
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
@GET(Constants.API_VERSION_V2 + "/products/{storeCode}/search")
fun searchProductV2(
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
-----------------------
@GET("{baseUrl}products/{storeCode}/search")
fun searchProductV2(
    @Path("baseUrl") baseUrl: String = "https://myBaseUrl/v2/",
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
@GET(Constants.API_BASE_URL_V2 + "products/{storeCode}/search")
fun searchProductV2(
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
@GET(Constants.API_VERSION_V2 + "/products/{storeCode}/search")
fun searchProductV2(
    @Path("storeCode") storeCode: String = "default",
    ...
): Single<Products>
-----------------------
@GET
fun searchProductV2(
    @Url fullPath: String,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = "https://myBaseUrl/v2/products/%s/search".format("storeCode")
@GET
fun searchProductV2(
    @Url fullPath: Uri,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = Uri.parse("https://myBaseUrl/v2/products/%s/search".format("storeCode"))
class DynamicUrl(private val formatString: String, private vararg val args: Any?) {
    fun toUri(): Uri = Uri.parse(formatString.format(*args))
}
val fullPath = DynamicUrl("https://myBaseUrl/v2/products/%s/search", "storeCode").toUri()
-----------------------
@GET
fun searchProductV2(
    @Url fullPath: String,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = "https://myBaseUrl/v2/products/%s/search".format("storeCode")
@GET
fun searchProductV2(
    @Url fullPath: Uri,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = Uri.parse("https://myBaseUrl/v2/products/%s/search".format("storeCode"))
class DynamicUrl(private val formatString: String, private vararg val args: Any?) {
    fun toUri(): Uri = Uri.parse(formatString.format(*args))
}
val fullPath = DynamicUrl("https://myBaseUrl/v2/products/%s/search", "storeCode").toUri()
-----------------------
@GET
fun searchProductV2(
    @Url fullPath: String,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = "https://myBaseUrl/v2/products/%s/search".format("storeCode")
@GET
fun searchProductV2(
    @Url fullPath: Uri,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = Uri.parse("https://myBaseUrl/v2/products/%s/search".format("storeCode"))
class DynamicUrl(private val formatString: String, private vararg val args: Any?) {
    fun toUri(): Uri = Uri.parse(formatString.format(*args))
}
val fullPath = DynamicUrl("https://myBaseUrl/v2/products/%s/search", "storeCode").toUri()
-----------------------
@GET
fun searchProductV2(
    @Url fullPath: String,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = "https://myBaseUrl/v2/products/%s/search".format("storeCode")
@GET
fun searchProductV2(
    @Url fullPath: Uri,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = Uri.parse("https://myBaseUrl/v2/products/%s/search".format("storeCode"))
class DynamicUrl(private val formatString: String, private vararg val args: Any?) {
    fun toUri(): Uri = Uri.parse(formatString.format(*args))
}
val fullPath = DynamicUrl("https://myBaseUrl/v2/products/%s/search", "storeCode").toUri()
-----------------------
@GET
fun searchProductV2(
    @Url fullPath: String,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = "https://myBaseUrl/v2/products/%s/search".format("storeCode")
@GET
fun searchProductV2(
    @Url fullPath: Uri,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = Uri.parse("https://myBaseUrl/v2/products/%s/search".format("storeCode"))
class DynamicUrl(private val formatString: String, private vararg val args: Any?) {
    fun toUri(): Uri = Uri.parse(formatString.format(*args))
}
val fullPath = DynamicUrl("https://myBaseUrl/v2/products/%s/search", "storeCode").toUri()
-----------------------
@GET
fun searchProductV2(
    @Url fullPath: String,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = "https://myBaseUrl/v2/products/%s/search".format("storeCode")
@GET
fun searchProductV2(
    @Url fullPath: Uri,
    @Query("searchCriteria[pageSize]") pageSize: String,
    @QueryMap apiSearchCriteria: Map<String, String>,
    @HeaderMap headerMap: Map<String, String> = emptyMap()
): Single<Products>
val fullPath = Uri.parse("https://myBaseUrl/v2/products/%s/search".format("storeCode"))
class DynamicUrl(private val formatString: String, private vararg val args: Any?) {
    fun toUri(): Uri = Uri.parse(formatString.format(*args))
}
val fullPath = DynamicUrl("https://myBaseUrl/v2/products/%s/search", "storeCode").toUri()

Return a zip (or any file) from the server on the client browser (REST)

copy iconCopydownload iconDownload
 @RequestMapping(method = RequestMethod.POST , params="action=downloadDocument")  
  public String downloadDocument(@RequestParam(value="documentId", required=true) String documentId,
                                HttpServletRequest request,
                                HttpServletResponse response ) 
  {     
    try {
        String docName = null;
        String documentSavePath = getDocumentSavePath();                    

    
        PDocument doc = mainService.getDocumentById(iDocumentId);
        
        if(doc==null){
            throw new RuntimeException("document with id: " + documentId + " not found!");
        }
        
        docName = doc.getName();
        String path = documentSavePath + ContextUtils.fileSeperator() +  docName;   
        response.setHeader("Content-Disposition", "inline;filename=\"" + docName + "\"");
        OutputStream out = response.getOutputStream();
        response.setContentType("application/word");
            
        FileInputStream stream = new FileInputStream(path);
        IOUtils.copy(stream, out);
        out.flush();
        out.close();
        
        } catch(FileNotFoundException fnfe){
                logger.error("Error downloading document! - document not found!!!! " + fnfe.getMessage() , fnfe);           
        } catch (IOException e) {
                logger.error("Error downloading document!!! " + e.getMessage(),e);          
        }
        return null;
    }
-----------------------
return this.http.get(URL_API_REST + 'download?filename=' + filename, {
    responseType: 'arraybuffer'
  });
-----------------------
let observable = this.downSvc.download(opts);
    this.handleData(observable, (data) => {
      let content = data;
      const blob = new Blob([content], { type: 'application/pdf' });
      saveAs(blob, file);
    });
public void download(HttpServletRequest request,HttpServletResponse response){
      ....
        response.setHeader("Content-Disposition",
          "attachment;filename=\"" + fileName + "\"");
          try (
              OutputStream os = response.getOutputStream();
              InputStream is = new FileInputStream(file);) {
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = is.read(buf)) > -1) {
              os.write(buf, 0, len);
            }
            os.flush();
    }
-----------------------
let observable = this.downSvc.download(opts);
    this.handleData(observable, (data) => {
      let content = data;
      const blob = new Blob([content], { type: 'application/pdf' });
      saveAs(blob, file);
    });
public void download(HttpServletRequest request,HttpServletResponse response){
      ....
        response.setHeader("Content-Disposition",
          "attachment;filename=\"" + fileName + "\"");
          try (
              OutputStream os = response.getOutputStream();
              InputStream is = new FileInputStream(file);) {
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = is.read(buf)) > -1) {
              os.write(buf, 0, len);
            }
            os.flush();
    }
-----------------------
public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
-----------------------
public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
-----------------------
public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
-----------------------
public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
-----------------------
public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
-----------------------
public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});
server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}

Community Discussions

Trending Discussions on retrofit
  • Retrofit OkHttp - &quot;unexpected end of stream&quot;
  • A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction?java.lang.reflect.Invocation?
  • OkHttpClient sometimes getting incomplete json response
  • android datastore-preferences: Property delegate must have a 'getValue(Context, KProperty&lt;*&gt;)' method
  • android:exported needs to be explicitly specified for &lt;activity&gt;. Apps targeting Android 12 and higher are required to specify
  • One or more issues found when checking AAR metadata values:
  • Android Build Error: &quot;lStar not found...&quot;
  • Could not resolve com.google.guava:guava:30.1-jre - Gradle project sync failed. Basic functionality will not work properly - in kotlin project
  • Hilt circular dependency
  • Data Class with Nullable Parameter in Kotlin 1.5.0 &amp; 1.6.0 Throwing BackendException
Trending Discussions on retrofit

QUESTION

Retrofit OkHttp - &quot;unexpected end of stream&quot;

Asked 2022-Mar-27 at 18:38

I am getting "Unexpected end of stream" while using Retrofit (2.9.0) with OkHttp3 (4.9.1)

Retrofit configuration:

interface ApiServiceInterface {

    companion object Factory{

        fun create(): ApiServiceInterface {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY

            val client = OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30,TimeUnit.SECONDS)
                .addInterceptor(Interceptor { chain ->
                    chain.request().newBuilder()
                        .addHeader("Connection", "close")
                        .addHeader("Accept-Encoding", "identity")
                        .build()
                        .let(chain::proceed)
                })
                .retryOnConnectionFailure(true)
                .connectionPool(ConnectionPool(0, 5, TimeUnit.MINUTES))
                .protocols(listOf(Protocol.HTTP_1_1))
                .build()
            val gson = GsonBuilder().setLenient().create()
            val retrofit = Retrofit.Builder()
                    .addCallAdapterFactory(CoroutineCallAdapterFactory())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .baseUrl("http://***.***.***.***:****")
                    .client(client)
                    .build()
            return retrofit.create(ApiServiceInterface::class.java)
        }
    }


    @Headers("Content-type: application/json", "Connection: close", "Accept-Encoding: identity")
    @POST("/")
    fun requestAsync(@Body data: JsonObject): Deferred<Response>
}

So far I have found out the following:

  1. This issue only occurs for me while using Android Studio emulators running from Windows series OS (7, 10, 11) - this was reproduced on 2 different laptops from different networks.
  2. If running Android Studio emulators under OS X the issue won't reproduce in 100% cases.
  3. ARC/Postman clients never has any issues completing same requests to my backend.
  4. On running from Windows Android Studio emulators this issue reproduces in about 10-50% requests, other requests work without problem.
  5. The identical requests can result in this error or complete sucessfully.
  6. Responses which take about 11 sec to complete can result in success, while responses which take about 100 msec to complete can result in this error.
  7. Commenting off .client(client) from retrofit configuration eliminates this issue, but I loose the opportunity to use interceptors and other OkHttp functionality.
  8. Adding headers (Connection: close, Accept-Encoding: identity) does not solve issue.
  9. Turning retryOnConnectionFailure on or off has no impact on issue as well.
  10. Changing HttpLoggingInterceptor level or removing it completely does not solve issue.

Server-side configuration:

const http = require('http');
const server = http.createServer((req, res) => {

    const callback = function(code, request, data) {
        let result = responser(code, request, data);
        res.writeHead(200, {
            'Content-Type' : 'x-application/json',
            'Connection': 'close',
            'Content-Length': Buffer.byteLength(result)
        });
        res.end(result);
    };

    ...
}

server.listen(process.env.PORT, process.env.HOSTNAME, () => {
    console.log(`Server is running`);
});

So, based on 1,2,3 - this is unlikely server-side issue.
Based on 4, 5, 6 - it is not malformed request related or execution time related issue. Guessing from 7 - this issue roots lay in OkHttp rather than Retrofit itself.

I have read almost half of stackoverflow is search of resolution, like:
unexpected end of stream retrofit
Retrofit OkHttp unexpected end of stream on Connection error
and also discussion at OkHttp on Github:
https://github.com/square/okhttp/issues/3682
https://github.com/square/okhttp/issues/3715
But nothing helped so far.

Any idea what might be causing the problem?

Update

I've got more info on situation.

First, I changed headers on backend to not to pass Content-Length and pass Transfer-Encoding : identity instead. I don't know why, but Postman gives an error if theese headers are present both, saying it is not right.

res.writeHead(200, {
            'Content-Type' : 'x-application/json',
            'Connection': 'close',
            'Transfer-Encoding': 'identity'
        });

After that I started to receive another error on Windows hosted Android Studio emulators (with equal ratio of fail / success to "Unexpected end of stream")

2021-12-09 14:58:19.696 401-401/? D/P2P-> FRG DEBUG:: java.io.EOFException: End of input at line 1 column 1807 path $.meta
        at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1397)
        at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:483)
        at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:415)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:216)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
        at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

Spending a lot of time debugging this issue I have found that this exception was generated by JsonReader.java in method nextNonWhitespace where it try to to get colons, double quotes and curly or square braces to compose json object from decoded as char array buffer.

This buffer itself is received in fillBuffer method of the same module and it has length limit of 1024 elements. In my case the backend response is longer that this value (1807 chars), so while JsonReader.java parses my response as json object it do this in 2 iterations.

Each iteration it fills the buffer here:

int total;
while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
    limit += total;

    // if this is the first read, consume an optional byte order mark (BOM) if it exists
    if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\ufeff') {
        pos++;
        lineStart++;
        minimum++;
    }

    if (limit >= minimum) {
        return true;
    }
}

the read method is called on ResponseBody.kt class from okhttp3

@Throws(IOException::class)
    override fun read(cbuf: CharArray, off: Int, len: Int): Int {
      if (closed) throw IOException("Stream closed")

      val finalDelegate = delegate ?: InputStreamReader(
          source.inputStream(),
          source.readBomAsCharset(charset)).also {
        delegate = it
      }
      return finalDelegate.read(cbuf, off, len)
    }

The main problem is:

At first iteration all goes well, ResponseBody.kt "reads" first 1024 chars and gives them to JsonReader.java where it composes a part of response object.

1st iteration

When second iteration comes ResponseBody.kt "reads" the last part of response and fills with it the start of char buffer, so char buffer now contains as its first elements the tail of response, and after that - all elements which was left there after firts iteration.

The main problem is that it im most cases (about 80%) looses last char from response, in about 10% in looses 2 last chars from response and in about 10% it reads all chars. Here is shots:

Wrong number of chars It must contains 783 chars to complete json, but as shown at line 1290 it receives only 782.

Looking at buffer itself Wrong buffer
the char at 782 index (783 in order) must be second curly brace that closes json root, but instead of it there are leftovers from first iteration started. This results in exception mentioned above.

Now if we look at situation where requests finished successfully: Good number of chars With the same request it occasionly returns valid number of chars: 783

And the buffer itself is: enter image description here Now the second brace is present where it must be.

In this case request will be successfull.

The same response ending from Postman: postman The Postman success rate in parsing response is 100%, the same is true for OS X hosted android studio emulators and real devices I've used.

Update 2

It seems full buffer obtained in RealBufferedSource.kt:

internal inline fun RealBufferedSource.commonSelect(options: Options): Int {
  check(!closed) { "closed" }

  while (true) {
    val index = buffer.selectPrefix(options, selectTruncated = true)
    when (index) {
      -1 -> {
        return -1
      }
      -2 -> {
        // We need to grow the buffer. Do that, then try it all again.
        if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1
      }
      else -> {
        // We matched a full byte string: consume it and return it.
        val selectedSize = options.byteStrings[index].size
        buffer.skip(selectedSize.toLong())
        return index
      }
    }
  }
}

and here it is already missing last char: enter image description here

Update 3

Found this unsolved question which is exactly the same behavior:
Retrofit Json data truncated
Also comment from Android Studio emulators issues tracker:
https://issuetracker.google.com/issues/119027639#comment9

ANSWER

Answered 2022-Mar-27 at 18:38

OK, It took some time, but I've found what was going wrong and how to workaround that.

When Android Studio's emulators running in Windows series OS (checked for 7 & 10) receive json-typed reply from server with retrofit it can with various probability loose 1 or 2 last symbols of the body when it is decoded to string, this symbols contain closing curly brackets and so such body could not be parsed to object by gson converter which results in throwing exception.

The idea of workaround I found is to add an interceptor to retrofit which would check the decoded to string body if its last symbols match those of valid json response and add them if they are missed.

interface ApiServiceInterface {

    companion object Factory{

        fun create(): ApiServiceInterface {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY
            
            val stringInterceptor = Interceptor { chain: Interceptor.Chain ->
                val request = chain.request()
                val response = chain.proceed(request)
                val source = response.body()?.source()
                source?.request(Long.MAX_VALUE)
                val buffer = source?.buffer()
                var responseString = buffer?.clone()?.readString(Charset.forName("UTF-8"))
                if (responseString != null && responseString.length > 2) {
                    val lastTwo = responseString.takeLast(2)
                    if (lastTwo != "}}") {
                        val lastOne = responseString.takeLast(1)
                        responseString = if (lastOne != "}") {
                            "$responseString}}"
                        } else {
                            "$responseString}"
                        }
                    }
                }
                val contentType = response.body()?.contentType()
                val body = ResponseBody.create(contentType, responseString ?: "")
                return@Interceptor response.newBuilder().body(body).build()
            }

            val client = OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30,TimeUnit.SECONDS)
                .addInterceptor(interceptor)
                .addInterceptor(stringInterceptor)
                .retryOnConnectionFailure(true)
                .connectionPool(ConnectionPool(0, 5, TimeUnit.MINUTES))
                .protocols(listOf(Protocol.HTTP_1_1))
                .build()
            val gson = GsonBuilder().create()
            val retrofit = Retrofit.Builder()
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addConverterFactory(ScalarsConverterFactory.create())
                .baseUrl("http://3.124.6.203:5000")
                .client(client)
                .build()
            return retrofit.create(ApiServiceInterface::class.java)
        }
    }


    @Headers("Content-type: application/json", "Connection: close", "Accept-Encoding: identity")
    @POST("/")
    fun requestAsync(@Body data: JsonObject): Deferred<Response>
}

After this changes the issue didn't occure.

Source https://stackoverflow.com/questions/70246508

Community Discussions, Code Snippets contain sources that include Stack Exchange Network

Vulnerabilities

No vulnerabilities reported

Install retrofit

Download [the latest JAR][2] or grab from Maven central at the coordinates com.squareup.retrofit2:retrofit:2.9.0. Snapshots of the development version are available in [Sonatype’s snapshots repository][snap]. Retrofit requires at minimum Java 8+ or Android API 21+.

Support

For any new features, suggestions and bugs create an issue on GitHub. If you have any questions check and ask questions on community page Stack Overflow .

DOWNLOAD this Library from

Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases

Save this library and start creating your kit

Explore Related Topics

Share this Page

share link
Consider Popular HTTP Client Libraries
Compare HTTP Client Libraries with Highest Support
Compare HTTP Client Libraries with Permissive License
Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases

Save this library and start creating your kit

  • © 2022 Open Weaver Inc.