I'm trying to build a network module for Multiplatform project with ktor.
My code for GET request is something like this:
val result = httpClient.get<HttpResponse> {
url {
protocol = baseProtocol
host = baseUrl
encodedPath = urlPath
}
}
In some point my path contain a user id like this /users/{user_id}.
I can do a search and replace in string and replace this user_id with actual value, BUT is there any other way to do this? any ktor specific way.
For example with Retrofit we have this:
#GET("users/{user_id}/")
SomeData getUserData(#Path("user_id") String userId);
EDIT: adding more code
val result = httpClient.get<HttpResponse> {
url {
protocol = baseProtocol
host = baseUrl
var requestPath = request.requestPath.value
request.path?.forEach {
requestPath = requestPath.replace(it.first, it.second)
}
encodedPath = requestPath
if (request.parameters != null) {
parameters.appendAll(getParametersFromList(request.parameters))
}
}
the request.path?.forEach { requestPath = requestPath.replace(it.first, it.second)} replacing any runtime path value.
Related
I want to send data to my url using Kotlin. How can I do it? I use Retrofit with field post but didn't work. Http request may be work but I couldn't find a way fits. Should sending datas be json or not?
MainActivity
lifecycleScope.launch {
doPostRequest()
}
private suspend fun doPostRequest() {
withContext(Dispatchers.IO) {
//
}
}
My Php file
<?php
$tuccar_id = $_POST['merchant_id'];
$tuccar_key = $_POST['merchant_key'];
$tuccar_salt = $_POST['merchant_salt'];
$tuccar_oid = $_POST['merchant_oid'];
$sepet = $_POST['user_basket'];
$mail = $_POST['email'];
$odeme_tutari = $_POST['payment_amount'];
$sepet = $_POST['user_basket'];
$kartsahibi = $_POST['cc_owner'];
$kartno = $_POST['card_number'];
$kartay = $_POST['expiry_month'];
$kartyil = $_POST['expiry_year'];
$kartcvv = $_POST['cvv'];
$usern = $_POST['user_name'];
$useradd = $_POST['user_address'];
$usertel = $_POST['user_phone'];
$user_basket = htmlentities(json_encode(array(
array($sepet, $odeme_tutari, 1)
)));
print_r($_POST);
$result = 1;
return $result;
?>
I am trying to create an android app with kotlin, this app need to have a mini download manager as I will need to download files from 100MB to 8GB and user can pause and resume download later when the server supports the pause, searching I found the Ktor library and reading the documentation plus some videos on youtube, I managed to write a base code where I could download the files and make the process of stopping the download and keep going all right when one of mine tests gave error there are files whose url pattern is: http://server.com/files?file=/10/55/file.zip
The problem is that I put this link, but Ktor converts to http://server.com/files?file=%2F10%2F55%2Ffile.zip this generate an error response on the server, as I don't have access to the server to change this rule I need to send the right url without encoding. Does anyone know how to do this? Prevent Ktor from doing a URL_encode in the url parameters, I couldn't find anything in the documentation
My code is this:
ktor-client version 1.6.7
fun startDownload(url: String, auth: String = "", userAgentS: String = "", fileName: String = ""){
val client = HttpClient(CIO)
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File.createTempFile("File", "index", path)
runBlocking {
client.get<HttpStatement>(url){
headers {
append(HttpHeaders.Authorization, auth)
append(HttpHeaders.UserAgent, userAgentS)
append(HttpHeaders.Range, "bytes=${file.length()}-")
}
}
.execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.receive()
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
while (!packet.isEmpty) {
val bytes = packet.readBytes()
file.appendBytes(bytes)
println("Received ${(file.length())} bytes from ${httpResponse.contentLength()}")
}
}
val pathF = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/${fileName}")
file.renameTo(pathF)
println("A file saved to ${file.path}")
}
}
}
Can anyone help me solve this problem with ktor, if there is no solution, can someone tell me another way to achieve the same goal? Need to be with Kotlin.
update 2022-02-17
Thanks to Aleksei Tirman's help I managed to solve the problem, thank you very much. And the base code looks like this:
fun startDownload(url: String, auth: String = "", userAgentS: String = "", fileName: String = ""){
val client = HttpClient(CIO)
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File.createTempFile("File", "index", path)
runBlocking {
client.get<HttpStatement>(url){
url {
parameters.urlEncodingOption = UrlEncodingOption.NO_ENCODING
}
headers {
append(HttpHeaders.Authorization, auth)
append(HttpHeaders.UserAgent, userAgentS)
append(HttpHeaders.Range, "bytes=${file.length()}-")
}
}
.execute { httpResponse ->
val channel: ByteReadChannel = httpResponse.receive()
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
while (!packet.isEmpty) {
val bytes = packet.readBytes()
file.appendBytes(bytes)
println("Received ${(file.length())} bytes from ${httpResponse.contentLength()}")
}
}
val pathF = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/${fileName}")
file.renameTo(pathF)
println("A file saved to ${file.path}")
}
}
}
You can disable query parameters encoding by assigning the UrlEncodingOption.NO_ENCODING value to the urlEncodingOption property of the ParametersBuilder. Here is an example:
val requestBuilder = HttpRequestBuilder()
requestBuilder.url {
protocol = URLProtocol.HTTP
host = "httpbin.org"
path("get")
parameters.urlEncodingOption = UrlEncodingOption.NO_ENCODING
parameters.append("file", "/10/55/file.zip")
}
val response = client.get<String>(requestBuilder)
I'm using Xamarin Forms to consume REST Api from NetFlix but i get this issue in Popup:
System.Net.WebException: Error: NameResolutionFailure
Why o get this error?
My Code:
private HttpClient client = new HttpClient();
private List<Movie> movies;
public async Task<List<Movie>> LocalizaFilmesPorAtor(string ator)
{
if (string.IsNullOrWhiteSpace(ator))
{
return null;
}
else
{
string url = string.Format("http://netflixroulette.net/api/api.php?actor={0}", ator);
var response = await client.GetAsync(url);
if (response.StatusCode == HttpStatusCode.NotFound)
{
movies = new List<Movie>();
} else
{
var content = await response.Content.ReadAsStringAsync();
var _movies = JsonConvert.DeserializeObject<List<Movie>>(content);
movies = new List<Movie>(_movies);
}
return movies;
}
}
In debug mode said the error is in this code
string url = string.Format("http://netflixroulette.net/api/api.php?actor={0}", ator);
var response = await client.GetAsync(url);
He stops in there, the url recive the url + actor name but in next line the response stay null.
PS: I give Internet permission to my App in Manifest!
nuget packages: Microsoft HTTP Client Libraries and Newtonsoft.Json.
try this:
private HttpClient client = new HttpClient();
private List<Movie> movies;
public async Task<List<Movie>> LocalizaFilmesPorAtor(string ator)
{
if (string.IsNullOrWhiteSpace(ator))
{
return null;
}
else
{
var client = new HttpClient();
client.BaseAddress = new Uri("http://netflixroulette.net/");
HttpResponseMessage response = client.GetAsync("api/api.php?actor={0}", ator);
if(response.IsSuccessStatusCode)
{
var json = response.Content.ReadAsStringAsync().Result;
var _movies = JsonConvert.DeserializeObject<List<Movie>>(json);
movies = new List<Movie>(_movies);
}
else
{
movies = new List<Movie>();
}
return movies;
}
}
PS: if movies = new List(_movies) not work, try foreach.
Why cant you try Refit?
Refit is a library heavily inspired by Square's Retrofit library, and it turns your REST API into a live interface
What you just need to do is:
Add Refit from Nuget Package
Create an Interface with any name
Import the Refit (Using Refit)
here is a sample code for the interface
public interface ISampleName
{
[Get("api/api.php?actor={ator}")]
async Task<List<Movie>> LocalizaFilmesPorAtor(string ator);
}
After that, then you can call it this way:
var SampleNameApi = RestService.For<ISampleName>("http://netflixroulette.net/");
var response= await SampleNameApi.LocalizaFilmesPorAtor("Sample");
I believe this will help you.
For More Information https://github.com/paulcbetts/refit
I had this issue too because my URL was malformed. Double check if your URL is correct with Postman/Browser.
I have an url http://example.com/{x}/push/{y} and I'm using OkHttp curl it.
final HttpUrl httpUrl = HttpUrl
.parse("http://example.com/{x}/push/{y}")
.newBuilder()
???
.build();
Is it possible to set these {x} and {y} path params?
I can see method like addPathSegment which is somehow related, but not what I want.
Here’s one technique that might help you to get started.
HttpUrl template = HttpUrl.parse("http://example.com/{a}/b/{c}");
HttpUrl.Builder builder = template.newBuilder();
for (int i = 0; i < template.pathSegments().size(); i++) {
String parameter = template.pathSegments().get(i);
String replacement = null;
if (parameter.equals("{a}")) {
replacement = "foo";
} else if (parameter.equals("{c}")) {
replacement = "bar";
}
if (replacement != null) {
builder.setPathSegment(i, replacement);
}
}
HttpUrl url = builder.build();
Maybe HttpUrl.setPathSegment(index, value) can make it look a bit better :D
fun HttpUrl.insertPathSegment(index: Int, pathSegment: String): HttpUrl {
val newPathSegments: ArrayList<String> =
encodedPathSegments().fold(ArrayList()) { acc, oldPathSegment ->
printLog("OkHttp", "insertPathSegment oldPathSegment:$oldPathSegment ")
acc.add(oldPathSegment)
acc
}
return newBuilder().apply {
try {
newPathSegments.add(index, pathSegment)
addEncodedPathSegment("")
newPathSegments.forEachIndexed { index, path ->
printLog("OkHttp", "insertPathSegment setEncodedPathSegment:$index $path ")
setEncodedPathSegment(index, path)
//printLog("OkHttp", "insertPathSegment setPathSegment:$index $path ")
//setPathSegment(index, path)
}
} catch (e: Exception) {
e.printStackTrace()
}
}.build()}
I used the following approach to pass path variables:
inputUrl= http://localhost:8080/getResults/firstName/%s/lastName/%s
HttpUrl url = HttpUrl.get(String.format(inputUrl, "fn","ln"));
Request request = new Request.Builder().url(url).build();
i am trying to authenticate something(in this case LinkedIn) using OAuth but the requested token always returns null?
Here is my code below:
public void authenticateAppOauthApi() {
Log.d(TAG, "authenticateAppOauthApi");
OAuthServiceProvider provider = new OAuthServiceProvider(
REQUEST_TOKEN_PATH, AUTHORIZE_PATH, ACCESS_TOKEN_PATH);
OAuthConsumer consumer = new OAuthConsumer(CALLBACK_URL, API_KEY,
SECRET_KEY, provider);
OAuthAccessor accessor = new OAuthAccessor(consumer);
Intent intent = new Intent(Intent.ACTION_VIEW);
Log.d(TAG, "Intent intent = new Intent(Intent.ACTION_VIEW );");
// intent.setData(Uri.parse(url));
String url = accessor.consumer.serviceProvider.userAuthorizationURL
+ "?oauth_token=" + accessor.requestToken + "&oauth_callback="
+ accessor.consumer.callbackURL;
intent.setData(Uri.parse(url));
Log.d(TAG, "intent.setData(Uri.parse(url)); = " + url);
mContext.startActivity(intent);
Log.d(TAG, "finish authenticateApp");
}
I basicaly followed the example here http://donpark.org/blog/2009/01/24/android-client-side-oauth
thanks in advance
you can try this code.
OAuthClient oAuthClient = new OAuthClient(new HttpClient4());
try {
oAuthClient.getRequestToken(accessor);
} catch (IOException e) {
e.printStackTrace();
} catch (OAuthException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
Just a idea, Is it HTTP URL or HTTPS URL ?
I had some problem to access HTTPS URL, browser & app told that certificate was wrong.
Some Root Certificate are not known by Android.
I had some trouble using one of the OAuth libs I found on the net in my Scala Android app. Instead of finding a way to use the lib I just rolled my own... Not sure if it would work against linkedin (it works well with Yammer, which uses HTTPS). Well, below is the relevant code, mind you I'm pretty new to both Android and Scala so there are probably better ways to accomplish this.
The layout file "R.layout.authorization" is very basic contains with two buttons and a text field in a RelativeLayout.
class Authorization extends Activity {
val client = new DefaultHttpClient()
val reqUrl = "https://www.yammer.com/oauth/request_token"
val authUrl = "https://www.yammer.com/oauth/authorize?oauth_token="
val accessUrl = "https://www.yammer.com/oauth/access_token"
override def onCreate(bundle:Bundle) = {
super.onCreate(bundle)
this.setContentView(R.layout.authorization)
val authButton = findViewById(R.id.authButton).asInstanceOf[Button]
val getCodeButton = findViewById(R.id.getCode).asInstanceOf[Button]
val prefs = getSharedPreferences(PreferenceFile(), 0)
if(prefs.contains("oauth_request_token")) {
authButton.setVisibility(View.VISIBLE)
}
setupListeners(authButton, getCodeButton)
}
private def getAuthVerifier() = {
val authVerifierBox:EditText = Authorization.this.findViewById(R.id.authVerifier).asInstanceOf[EditText]
if(authVerifierBox != null && authVerifierBox.getText() != null) {
authVerifierBox.getText().toString()
} else {
""
}
}
private def setupListeners(authButton:Button, getCodeButton:Button) = {
authButton.setOnClickListener(new View.OnClickListener() {
override def onClick(view:View) = {
retrieveAuthTokenAndSecret()
}
})
getCodeButton.setOnClickListener(new View.OnClickListener() {
override def onClick(view:View) = {
try {
// Retrieve a request token with an async task and then start the browser...
// Use of an implicit definition to convert tuple to an async task.
(() => {
// Task to perform
val reqPost = new HttpPost(reqUrl)
reqPost.setHeader("Authorization", OAuthHeaderBuilder(null,null,null))
reqPost.setHeader("Content-Type", "application/x-www-form-urlencoded")
val reqResp= client.execute(reqPost)
reqResp.getEntity()
},
(entity:HttpEntity) => {
// PostExecute handle result from task...
if(entity != null) {
val reader = new BufferedReader(new InputStreamReader(entity.getContent()))
val line = reader.readLine()
val (oauth_request_token, oauth_token_secret) = OAuthTokenExtractor(line)
// Store request tokens so they can be used when retrieving auth tokens...
val editor = getSharedPreferences(PreferenceFile(), 0).edit()
editor.putString("oauth_request_token", oauth_request_token)
editor.putString("oauth_token_secret", oauth_token_secret)
editor.commit()
// Start browser...
val intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl + oauth_request_token))
startActivity(intent)
val authButton = findViewById(R.id.authButton).asInstanceOf[Button]
authButton.setVisibility(View.VISIBLE)
}
}).doInBackground()
} catch {
case e:Exception => Log.e("ERROR", "ERROR IN CODE:"+e.toString())
}
}
})
}
private def retrieveAuthTokenAndSecret() = {
val authVerifier = getAuthVerifier()
val accessPost = new HttpPost(accessUrl)
val prefs = getSharedPreferences(PreferenceFile(), 0)
val token = prefs.getString("oauth_request_token","")
val secret = prefs.getString("oauth_token_secret","")
accessPost.setHeader("Authorization", OAuthHeaderBuilder(token, secret, authVerifier))
accessPost.setHeader("Content-Type", "application/x-www-form-urlencoded")
val accessResp = client.execute(accessPost)
val entity = accessResp.getEntity()
if(entity != null) {
val reader = new BufferedReader(new InputStreamReader(entity.getContent()))
val builder = new StringBuilder()
val line = reader.readLine()
val (oauth_token, oauth_token_secret) = OAuthTokenExtractor(line)
val result = new Intent()
val editor = getSharedPreferences(PreferenceFile(), 0).edit()
editor.putString("oauth_token", oauth_token)
editor.putString("oauth_token_secret", oauth_token_secret)
editor.commit()
setResult(Activity.RESULT_OK, result)
finish()
}
}
}
The OAuthHeaderBuilder is basically a copy of the Yammer oauth sample code:
object OAuthHeaderBuilder {
// Apply function taken from the Yammer oauth sample
def apply(token:String, secret:String,verifier:String):String = {
val buff = new StringBuilder()
val currentTime = System.currentTimeMillis()
// Hardcoded values for consumer key and secret...
val consumerKey = "<your consumer key here>"
val consumerSecret = "<your consumer secret here>"
buff.append("OAuth realm=\"");
buff.append("\", oauth_consumer_key=\"");
buff.append(consumerKey);
buff.append("\", ");
if (token != null) {
buff.append("oauth_token=\"");
buff.append(token);
buff.append("\", ");
}
buff.append("oauth_signature_method=\"");
buff.append("PLAINTEXT");
buff.append("\", oauth_signature=\"");
buff.append(consumerSecret);
buff.append("%26");
if (secret != null) {
buff.append(secret);
}
buff.append("\", oauth_timestamp=\"");
buff.append(currentTime);
buff.append("\", oauth_nonce=\"");
buff.append(currentTime);
if (verifier != null) {
buff.append("\", ");
buff.append("oauth_verifier=\"");
buff.append(verifier);
}
buff.append("\", oauth_version=\"1.0\"");
return buff.toString();
}
}
And inorder to extract the tokens I made a OAuthTokenExtractor object...
object OAuthTokenExtractor {
def apply(line:String) = {
val token = (line split ("&")).find(x => x.startsWith("oauth_token=")) match {
case Some(oauth_token) => (oauth_token split ("=") )(1)
case _ => ""
}
val secret = (line split ("&")).find(x => x.startsWith("oauth_token_secret=")) match {
case Some(oauth_token_secret) => (oauth_token_secret split ("=") )(1)
case _ => ""
}
(token,secret)
}
}
Hope it helps :)
Better use this article as a reference:
There is a code to get authentication URL in current version of Signpost is:
provider.retrieveRequestToken(CALLBACK_URL);
(and be sure to use CommonsHttpOAuthConsumer and CommonsHttpOAuthProvider)