I have an android application I am trying to sync with a rails app.
In the android app I download and load a json object from the server (using an OAuth token for authentication):
private JSONArray getJSON(URL url, String authToken) throws IOException, JSONException {
URLConnection con = url.openConnection();
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", "Bearer " + authToken);
InputStream is = new BufferedInputStream(con.getInputStream());
Scanner s = new Scanner(is).useDelimiter("\\A");
String text = s.hasNext() ? s.next() : "";
return new JSONArray(text);
}
I'm trying to reload the existing data and it is ~380k. When I run the code I get the following in the rails server log:
Started GET "/events.json?created_since=0" for 192.168.1.111 at 2014-01-22 20:15:16 -0500
Processing by EventsController#index as JSON
Parameters: {"created_since"=>"0", "event"=>{}}
Doorkeeper::AccessToken Load (0.6ms) SELECT "oauth_access_tokens".* FROM "oauth_access_tokens" WHERE "oauth_access_tokens"."token" = [:filtered:] ORDER BY "oauth_access_tokens"."id" ASC LIMIT 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 501661262]]
Habit Load (0.7ms) SELECT "habits".* FROM "habits" WHERE "habits"."user_id" = ? ORDER BY "habits"."id" ASC LIMIT 1000 [["user_id", 501661262]]
Event Load (26.7ms) SELECT "events".* FROM "events" WHERE "events"."habit_id" = ? [["habit_id", 1]]
⋮
Rendered events/index.json.jbuilder (3422.5ms)
Completed 200 OK in 4491ms (Views: 3436.2ms | ActiveRecord: 73.4ms)
[2014-01-22 20:15:21] ERROR Errno::ECONNRESET: Connection reset by peer
/home/will/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/httpserver.rb:80:in `eof?'
/home/will/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/httpserver.rb:80:in `run'
/home/will/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/webrick/server.rb:295:in `block in start_thread'
The connection reset is repeated seven times. The client receives about 260k of data. app/views/events/index.json.jbuilder is:
json.array!(#events) do |event|
json.extract! event, :id, :habit_id, :time, :description
end
The same method is used to load a different model with only a few entries and it loads correctly. Is there a limit to how big a file can be downloaded? In any case pagination seems like a good idea. Anyone know of any guidelines on what size chunks I ought to break it up into?
Instead using Scanner, maybe you can use Reader to read the result InputStream
Reader reader = new InputStreamReader(is);
JsonParser parser = new JsonParser();
JsonArray jsonArray = parser.parse(reader).getAsJsonArray();
I ended up paginating the data and it now loads correctly:
JSONArray events;
int page = 1;
SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
batch = new ArrayList<ContentProviderOperation>();
do {
events = getJSON(new URL(EVENT_READ_URL + "?page=" + (page++)), authToken);
for(int i = 0; i < events.length(); i++) {
JSONObject event = events.getJSONObject(i);
batch.add(ContentProviderOperation.newInsert(HabitContentProvider.EVENTS_URI)
.withValue(EventTable.COLUMN_ID, event.getInt("id"))
.withValue(EventTable.COLUMN_HABIT_ID, event.getInt(EventTable.COLUMN_HABIT_ID))
.withValue(EventTable.COLUMN_TIME, timeFormat.parse(event.getString(EventTable.COLUMN_TIME)).getTime() / 1000)
.withValue(EventTable.COLUMN_DESCRIPTION, event.getString(EventTable.COLUMN_DESCRIPTION))
.build());
}
} while(events.length() > 0);
try {
mContentResolver.applyBatch(HabitContentProvider.AUTHORITY, batch);
} catch(SQLiteConstraintException e) {
Log.e(TAG, "SQLiteConstraintException: " + e.getMessage());
}
In my rails code I added the will_paginate gem, included array support, and added the following to my code:
if params[:page]
#events = #events.paginate(page: params[:page], per_page: params[:per_page] || 300)
end
Related
I Have successfully implemented a realtime powerbi dashboard ( to monitor CPU and Ram usage ) through Rest API i have used the following powershell script to read values and sent these values via 3 variables Time , Ram and CPU through powershell code to a PowerBi provided end point , as follows ( the end point )
https://api.powerbi.com/beta/xxxxxx-xxxxx-xxxx-xxxx-xxxxxxxxxxxx/datasets/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx/rows?key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%xxxxxxx%xxxxx%xxxx%xxxxxxxxxxxxxxx%3D%3D
Seeking help want to send these variables through an android app the variables will remain same ( Time , CPU and RAm usage ) but this time it will be from android app ( app is already working fine and capturing the ram and CPU info through Java ,
i have tried Retrofit , Volley example codes but still not able to figure out that how i can send these 3 variables to following power BI streaming data sets end point ? I am new to REST so seeking help to send ( post these 3 variables in Json format to following power BI end point API as shown in the following powershell code.
I have also tried following HTTP based code but not able to figure out if i can put the following power BI push URL in the following code new HttpPost("http://yoururl"); replaced with power BI URL ?
JSONObject json = new JSONObject();
json.put("CPU", "15%");
json.put("RAM", "4 GB");
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
try {
HttpPost request = new HttpPost("http://yoururl");
StringEntity params = new StringEntity(json.toString());
request.addHeader("content-type", "application/json");
request.setEntity(params);
httpClient.execute(request);
// handle response here...
} catch (Exception ex) {
// handle exception here
} finally {
httpClient.close();
}
All i have got from power BI is following push URL
while($true)
{
$ComputerCPU = (Get-WmiObject -Class win32_processor -ErrorAction Stop | Measure-Object -Property LoadPercentage -Average | Select-Object Average).Average
$ComputerMemory = Get-WmiObject -Class win32_operatingsystem -ErrorAction Stop
$UsedMemory = $ComputerMemory.TotalVisibleMemorySize - $ComputerMemory.FreePhysicalMemory
$Memory = (($UsedMemory/ $ComputerMemory.TotalVisibleMemorySize)*100)
$RoundMemory = [math]::Round($Memory, 2)
$Date = Get-Date -DisplayHint Date -Format MM/dd/yyyy
$Time123 = Get-Date -DisplayHint Time -Format HH:MM:ss
#$Date
#$Time123
#$ComputerCPU
#$RoundMemory
$endpoint = "https://api.powerbi.com/beta/xxxxxxxxxxx/datasets/xxxxxxxx/rows?key=xxx%xxxxxx%xxxxxxx%xxxxxx%xxxxxxxx%3D%3D"
$payload = #{
"Date" =$Date
"Time" =$Time123
"CPU" = $ComputerCPU
"MEM" = $RoundMemory
}
Invoke-RestMethod -Method Post -Uri "$endpoint" -Body (ConvertTo-Json #($payload))
Write-Host "date: " $Date " time: " $Time123 " cpu: " $ComputerCPU " mem: " $RoundMemory
sleep 0.5
}
Got the way
its through using the OkHTTP.
For each request to server in my android app I need to encode parameters, so my string for URL is looks like
"http://example.com/script.php?param1="+URLEncoder.encode(param1.getText().toString(), "UTF-8")+"param2="+URLEncoder.encode(param2.getText().toString(), "UTF-8")+...."
It works but maybe it is possible to use URLEncoder.encode only one time - like this
URLEncoder.encode("http://example.com/script.php?param1="+param1.getText().toString()+"param2="+param2.getText().toString()+....", "UTF-8")
Would it be ok or there are some cases when it can crash?
URL encoding the whole URL will not work, because it would result in something like
"http%3A%2F%2Fexample.com%2Fscript.php%3Fparam1%3Dasdf%26param2%3Djkl"
i.e. all the special characters in the whole URL would be encoded. You also can not url encode the whole query string, because the = and & characters would be encoded.
You have to encode each parameter value to stop special characters in the parameter interfering with the URL parsing. A helper function may reduce the pain.
String url = "http://example.com/script.php?" + encodeArgs("a", "a + b", "b", "=xxx=");
and something to get you started
public String encodeArgs(String... args) {
final String encoding = "UTF-8";
try {
if (args.length % 2 != 0) {
throw new IllegalArgumentException("number of arguments not even");
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < args.length; i += 2) {
sb.append(URLEncoder.encode(args[i], encoding));
sb.append("=");
sb.append(URLEncoder.encode(args[i + 1], encoding));
sb.append("&");
}
// delete last &, if any
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("unsupported encoding " + encoding, e);
}
}
You should not encode complete URL. Encode only param section or in other words, only parts of it that come from "unreliable sources".
So your 1st attempt "http://example.com/script.php?param1="+URLEncoder.encode(param1.getText().toString(), "UTF-8")+"param2="+URLEncoder.encode(param2.getText().toString(), "UTF-8")+...." is correct, and you should continue with it.
URL encoding in Android and Android: howto parse URL String with spaces to URI object? can be useful for more clarity.
Here's my problem: I'm writing a laravel backend which have to serve an mp3 file that had to be reproduced by using the android standard media player.
For the laravel backend I need to use JWT to handle authentication so on every request headers I have to set the "Authorization" field to "Bearer {token}" .The laravel route is "/songs/{id}" and is handled in this way:
public function getSong(Song $song) {
$file = new File(storage_path()."/songs/".$song->path.".mp3");
$headers = array();
$headers['Content-Type'] = 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3';
$headers['Content-Length'] = $file->getSize();
$headers['Content-Transfer-Encoding'] = 'binary';
$headers['Accept-Range'] = 'bytes';
$headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0';
$headers['Connection'] = 'Keep-Alive';
$headers['Content-Disposition'] = 'attachment; filename="'.$song->path.'.mp3"';
$user = \Auth::user();
if($user->activated_at) {
return Response::download($file, $song->path, $headers);
}
\App::abort(400);
}
On the android side I'm using the MediaPlayer to stream the mp3 file in this way:
media_player = new MediaPlayer();
try {
media_player.setAudioStreamType(AudioManager.STREAM_MUSIC);
String token = getSharedPreferences("p_shared", MODE_PRIVATE).getString("token", null);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
media_player.setDataSource(
getApplicationContext(),
Uri.parse(ConnectionHelper.SERVER + "/songs/" + song.getId()),
headers
);
} catch (IOException e) {
finish();
Toast.makeText(
Round.this,
"Some error occurred. Retry in some minutes.",
Toast.LENGTH_SHORT
).show();
}
media_player.setOnCompletionListener(this);
media_player.setOnErrorListener(this);
media_player.setOnPreparedListener(this);
But every time I execute the code I get extra code -1005 on the error listener that means ERROR_CONNECTION_LOST.
The problem: Response::download(...) doesn't produce a stream, so I can't serve my .mp3 file.
The solution:
As Symfony HttpFoundation doc. says in the serving file paragraph:
"if you are serving a static file, you can use a BinaryFileResponse"
The .mp3 files I need to serve are statics in the server and stored in "/storage/songs/" so I decided to use the BinaryFileResponse, and the method for serving .mp3 became:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
[...]
public function getSong(Song $song) {
$path = storage_path().DIRECTORY_SEPARATOR."songs".DIRECTORY_SEPARATOR.$song->path.".mp3");
$user = \Auth::user();
if($user->activated_at) {
$response = new BinaryFileResponse($path);
BinaryFileResponse::trustXSendfileTypeHeader();
return $response;
}
\App::abort(400);
}
The BinaryFileResponse automatically handle the requests and allow you to serve the file entirely (by making just one request with Http 200 code) or splitted for slower connection (more requests with Http 206 code and one final request with 200 code).
If you have the mod_xsendfile you can use (to make streaming faster) by adding:
BinaryFileResponse::trustXSendfileTypeHeader();
The android code doesn't need to change in order to stream the file.
We usually get data from server response in android development.
/*
* get server response inputStream
*/
InputStream responseInputStream;
Solution1: get response string by multiple read.
/*
* get server response string
*/
StringBuffer responseString = new StringBuffer();
responseInputStream = new InputStreamReader(conn.getInputStream(),"UTF-8");
char[] charBuffer = new char[bufferSize];
int _postion = 0;
while ((_postion=responseInputStream.read(charBuffer)) > -1) {
responseString.append(charBuffer,0,_postion);
}
responseInputStream.close();
Solution2: get response only one read.
String responseString = null;
int content_length=1024;
// we can get content length from response header, here assign 1024 for simple.
responseInputStream = new InputStreamReader(conn.getInputStream(),"UTF-8");
char[] charBuffer = new char[content_length];
int _postion = 0;
int position = responseInputStream.read(charBuffer)
if(position>-1){
responseString = new String(charBuffer,0,position );
}
responseInputStream.close();
Which solution has better performance? why?
Notes: server response json format data that less than 1M bytes.
Why you're reinventing a wheel? ;)
If you're using HttpClient then just use EntityUtils.toString(...).
I guess you're using HttpURLConnection. Then look at EntityUtils.toString(...) from Apache HttpClient - source code. Your first approach is similar to it.
BTW, the second code is worse because:
new String(charBuffer,0,position ) runs garbage collector
In both and even in EntityUtils:
int content_length = 1024; in most cases 8192 is default for socket buffer, so your code might run while loop 8 times more often than it could.
I would recommend the second method IF you do not want to display the amount of data downloaded/transferred . As the object is read as a whole and since the size of your JSON string is comparable to 1M, it will take some time to download. At that time you can, atmost, put up a text for the user saying downloading... You cannot notify the user the amount downloaded.
But if you want to display the amount of data downloaded, use the first method that you gave. Where the you read the data from the server in parts. You can update the UI, with the amount downloaded. For eg 25 % downloaded...
char[] charBuffer = new char[bufferSize];
int _postion = 0;
int i=0;
while ((_postion=responseInputStream.read(charBuffer)) > -1) {
//((i*buffer_size)/content_length) * 100 % completed..
i++;
}
So, I would say the seconds method is better.
BTW Did you consider this?
ObjectInputStream in = new InputStreamReader(conn.getInputStream(),"UTF-8");
if(resposeCode==200)
{
String from_server=(String) in.readObject();
}
Reading the input String as an object. Any object whoe class implements serializable can be passed using ObjectOutputStream and received using ObjectInputStream()
I Think First one is good
Because, in First that will reading your response in char to char method .
Where , Second that will try to read whole response object or as key Filed of Object.
So ,As i think and as per my knowledge First is Better to camper with second.If anyone want to edit then it will truly appreciated.
I had a PHP API which showed a JSON Array, which I then read into an Android Application.
I since moved servers and the android application broke.
I assumed it was the Authentication and thought I would re-build the Android application (Was my first application and thought a re-write could make things better)
For some reason I am now getting this exception error
I read somewhere that I need to parse JSON_FORCE_OBJECT in the PHP json_encode
json_encode($arrMyData, JSON_FORCE_OBJECT);
But I am running PHP 5.2 (Options parameter came out in PHP 5.3)
My code for you to rip into
private void displayAllStories(){
String line;
int intNumStories = 0;
JSONObject arrAllStories;
LinearLayout storiesLayout = (LinearLayout) findViewById(R.id.lyoutStoriesMain);
storiesLayout.removeAllViewsInLayout();
try {
while((line = this.jsonResult.readLine()) != null){
JSONObject arrStories;
arrStories = new JSONObject(line.trim());
intNumStories = Integer.parseInt(arrStories.optString("NumStories"));
arrAllStories = arrStories.getJSONObject("StoryData");
this.strDebug += "We have "+intNumStories+"\n";
}
} catch (IOException e) {
this.strDebug += "Error (3) "+e.getLocalizedMessage()+"\n";
} catch (JSONException e) {
this.strDebug += "Error (4) "+e.getLocalizedMessage()+"\n";
}
}
And the encoded data from the website
{
"NumStories":1,
"StoryData":{
"Story0":{
"ID":"1020",
"OWERNAME":"Alicia",
"STORYMAIN":"Good evening my son was born with bilateral club feet. When he was a week old we started serial casting once a week for 3 months and then he was placed in braces for the next 6 months for a 23 hour period and then for the next 3 months just durning the night. This last visit the doctor said that he needs to have his tendons lengthened and he will go back into cast. After reading all of these articles I am a little scared on what will be best for him. It sounds like the risk of having the surgery are just as heavily weighed as just keeping him in AFO\\'s till he can make his own decision. I would like all advice whether it be positive or negative. Thank you in advance for your help.",
"STORYBRIEF":"Need reassurance that tendon lengthening is the best decision.",
"ADDEDDATE":"2011-12-12 00:51:16",
"CURRENTSTATUS":"n"
}
}
}
Sorry I should add, the code before this which procudes jsonResult is as follows
try{
URL url = null;
URLConnection urlConn = null;
InputStreamReader jsonIsr = null;
BufferedReader jsonBr = null;
//this.strDebug += "URL is "+this.strURL+"\n";
url = new URL(this.strURL);
urlConn = url.openConnection();
jsonIsr = new InputStreamReader(urlConn.getInputStream());
jsonBr = new BufferedReader(jsonIsr, 8192);
this.jsonResult = jsonBr;
return true;
}catch(MalformedURLException e){
this.strDebug += "JSON Error (1) "+e.getLocalizedMessage()+"\n";
}catch(IOException e){
this.strDebug += "JSON Error (2) "+e.getLocalizedMessage()+"\n";
}
}else{
strDebug = "NO URL Passed to JSON\n";
}
// EDIT 2
For those who asking
The error is as the title says
Error (4) A JSONObject text must being with '{' at character 1 of {"NumStories":1, "StoryData":........
Your code assumes that whole JSON data comes on one line: it iterates with readLine() but creates a new JSON object every time.
You are reading the data line by line and trying to convert each line into a JSON object. That won't work because a single line just contains a fragment of a complete JSON object.
I don't know what type jsonResult has. But you'll probably want to read the whole thing at once.
Your old web application probably produced JSON data without line break so a single line would contain a full JSON object.
i think you read the json file line by line and pass to the json object you should like this way the whole string you have to pass to the json object for parsing than only you getting the json
JSONObject arrStories = new JSONObject(jsonResult);
now get the object like this way
intNumStories = Integer.parseInt(arrStories.getString("NumStories"));
This code is going to break, if object takes more than one line (apparemtly it does). Your choices are:
Collect all the strings into string builder, the parse from this string ( http://developer.android.com/reference/org/json/JSONTokener.html )
Take GSON or my databinding layer ( https://github.com/ko5tik/jsonserializer ) and just parse stream into object.