404 Error when trying to post a file topic - desire2learn

I am trying to create content in my d2l orgUnit but I keep getting 404 Page not found back. I am calling this from the Android emulator. I've successfully created a module as well as a topic link using the POST APIs. I did look at this post 404 Posting Content to Desire2Learn which helped me get the module and link working, but I just can't get the uploading a file as a topic to work. I suspect it may be the URL as I wasn't sure what to put, so I put a relative path I created in the org unit. The post 404 Posting Content to Desire2Learn mentions to use "valid location URL to within the org unit's existing content space". I also tried the /content/enforced/... folder as URL to no avail. I'm not sure if this is the issue, or a red herring...
Here is my code:
String body = "--xxBoundaryxx " +
"Content-Type: application/json " +
"{" +
"\"Title\": \"Testing an upload\"," +
"\"ShortTitle\": \"test\"," +
"\"Type\": 1," +
"\"TopicType\": 1," +
"\"URL\": \"/test/\"," +
"\"StartDate\": null," +
"\"EndDate\": null," +
"\"IsHidden\": false," +
"\"IsLocked\": false" +
" } " +
"--xxBoundaryxx " +
"Content-Disposition: form-data; name=\"file 0\"; filename=\"test.txt\" " +
"Content-Type: text/plain " +
"This is my sample file to try it out.";
URI uri = userContext.createAuthenticatedUri("/d2l/api/le/1.1/{OrgID}/content/modules/{ModuleID}/structure/", "Post");
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost post = new HttpPost(uri);
post.addHeader("Content-Type", "multipart/mixed; boundary=xxBoundaryxx");
post.setEntity(new StringEntity(body));
HttpResponse response = httpClient.execute(post);
Log.i(TAG, "Statusline: " + response.getStatusLine());
Here is the resulting body (I put \r\n for line breaks as well, but it didn't help).
--xxBoundaryxx
Content-Type: application/json
{
"Title": "Testing an upload",
"ShortTitle": "test",
"Type": 1,
"TopicType": 1,
"URL": "/test/",
"StartDate": null,
"EndDate": null,
"IsHidden": false,
"IsLocked": false
}
--xxBoundaryxx
Content-Disposition: form-data; name="file 0"; filename="test.txt"
Content-Type: text/plain
This is my sample file to try it out.
what is going on? getStatusLine always returns 404 error... I know it is not a permission issue since I can create modules and link topics successfully using very similar code. Any guidance greatly appreciated.

You almost certainly need a terminating boundary on your POST body (--xxBoundaryxx--). For the Url property in the JSON block you send: you can send an Url that's not relative (as in the example in the docs); it also seems you can send just a file name (relative name) and the upload process puts the file in the root of the course's content area. I would fully expect that (a) if you don't have an absolute URL, it would use the course's root folder as the base part of the path, and (b) the upload API action will not create directories for you, but I haven't comprehensive testing around how the Url property gets handled.
I've tested this API action on our test servers, and it works (with a fully specified Url and just a file name for the Url). I've also updated the Valence docs to include a concrete example of the course content file topic upload packet: hope these two things help you out.

Related

Microsoft Graph (OneDrive) API - Resumable Upload Content-Type

I am trying to create the upload PUT request for the OneDrive API. It's the large file "resumable upload" version which requires the createUploadSession.
I have read the Microsoft docs here: As a warning the docs are VERY inaccurate and full of factual errors...
The docs simply say:
PUT
https://sn3302.up.1drv.com/up/fe6987415ace7X4e1eF866337Content-Length:
26Content-Range: bytes 0-25/128 <bytes 0-25 of the
file>
I am authenticated and have the upload session created, however when I pass the JSON body containing my binary file I receive this error:
{ "error": {
"code": "BadRequest",
"message": "Property file in payload has a value that does not match schema.", .....
Can anyone point me at the schema definition? Or explain how the JSON should be constructed?
As a side question, am I right in using "application/json" for this at all? What format should the request use?
Just to confirm, I am able to see the temp file created ready and waiting on OneDrive for the upload, so I know I'm close.
Thanks for any help!
If you're uploading the entire file in a single request then why do you use upload session when you can use the simple PUT request?
url = https://graph.microsoft.com/v1.0/{user_id}/items/{parent_folder_ref_id}:/{filename}:/content
and "Content-Type": "text/plain" header and in body simply put the file bytes.
If for some reason I don't understand you have to use single-chunk upload session then:
Create upload session (you didn't specified any problems here so i'm not elaborating)
Get uploadUrl from createUploadSession response and send PUT request with the following headers:
2.1 "Content-Length": str(file_size_in_bytes)
2.2 "Content-Range": "bytes 0-{file_size_in_bytes - 1}/{file_size_in_bytes}"
2.3 "Content-Type": "text/plain"
Pass the file bytes in body.
Note that in the PUT request the body is not json but simply bytes (as specified by the content-type header.
Also note that max chuck size is 4MB so if your file is larger than that, you will have to split into more than one chunks.
Goodlcuk

403 Forbidden for amazon s3 put api to upload file in Rails App

I have the following aws s3 api end point from third party application
https://xxx.s3.amazonaws.com/uploads/74d75512c28b49358846f959bd798536?Signature=lxBIZJD7DN4QK3LPmsHxR7D2eTA%3D&Expires=1506108780&AWSAccessKeyId=AKIAJG6Z6A5TUL7ULPXA
I am trying to make an PUT request with this url to upload an File, but always get the following error
#<Net::HTTPForbidden 403 Forbidden readbody=true>
Update And here is the response body
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the
signature you provided. Check your key and signing method.
</Message>
<AWSAccessKeyId>AKIAJG6Z6A5TUL7ULPXA</AWSAccessKeyId>
<StringToSign>
PUT
application/xml
1506144576
/xxx/uploads/93a64f8081804604be917b4185c5ed58
</StringToSign>
<SignatureProvided>8pwtC2vtN4LuOwGc887AlT2ZnUc=</SignatureProvided>
<StringToSignBytes>50 55 </StringToSignBytes>
<RequestId>B2B18369BA23A92F</RequestId>
<HostId>YCtjOJsfskITKxjW96ouZq1BV=</HostId>
</Error>
If I inspect the response it shows following data, but did not understood what's wrong with this? no other clue, anyone can help will be appreciated.
{
"x-amz-request-id" => [
[0] "385D5CADFE6175FC"
],
"x-amz-id-2" => [
[0] "4uD51Gb/rJy0QooQVmF25Qbp0E568bB9v1P4Grg9CTM2dJ/Iiccad/IyuuEnWDphlGZrr8ZUnQw="
],
"content-type" => [
[0] "application/xml"
],
"transfer-encoding" => [
[0] "chunked"
],
"date" => [
[0] "Fri, 22 Sep 2017 19:28:01 GMT"
],
"server" => [
[0] "AmazonS3"
]
}
I am running the following code snippet
uri = URI.parse(aws_api_end_point)
request = Net::HTTP::Put.new(uri)
request.body = File.read(file_path)
request.content_type = 'application/xml'
http = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)
response = http.request(request)
Note:: I am not using any aws SDK.
Request signing is deterministic -- for a given request at a given moment in time, there is exactly one valid signature. For all practical purposes, the opposite is also true -- for any signature, there's only one valid request you can make. Anything else, and the signature does not match.
The algorithm is designed not to be reverse-engineered, so we can't say what request they expected you to make when they gave you the signature.
But, we do have this. I believe some whitespace was lost, so I have added it back in:
<StringToSign>
PUT
application/x-www-form-urlencoded
1506144576
/xxx/uploads/93a64f8081804604be917b4185c5ed58
</StringToSign>
This is the request you made (not the request you were expected to make), converted to its canonical form using this pseudocode.
StringToSign =
HTTP-VERB + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Expires + "\n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
The most obvious candidate for a problem is Content-Type. It is almost a certainty that they did not expect you to use application/x-www-form-urlencoded because the S3 PUT Object operation does not use HTML Form uploads. S3 PUT expects the raw octets of the object in the request body, and a Content-Type header to match. S3 itself does not actually validate whether they match (header vs. body), but without at least the expected Content-Type header, the upload will be blocked.
If you are providing a Content-Type to the 3rd party API, then your upload needs to use that same type on the S3 upload, because the signature expects it.
Unfortunately, for troubleshooting purposes, there is an infinite number of things that can be done wrong to invalidate a request intended to be used with a pre-signed URL. The documentation from the third party should clarify the structure of the subsequent request they expect you to make.

How do you post video data from ios to Signed Url to Google Cloud Bucket?

I have a python method that successfully creates a GET Signed Url that will download the video that is in the Google Cloud Bucket.
def _MakeUrlForApp(self, verb, path, content_type='', content_md5=''):
"""Forms and returns the full signed URL to access GCS."""
base_url = '%s%s' % (self.gcs_api_endpoint, path)
signature_string = self._MakeSignatureString(verb, path, content_md5,
content_type)
signature_signed = self._Base64Sign(signature_string)
"""replace # with %40 - and + with %2 and == with %3D"""
signature_signed = signature_signed.replace("+", "%2B")
signature_signed = signature_signed.replace("/", "%2F")
signature_signed = signature_signed.replace("=", "%3D")
self.client_id_email = self.client_id_email.replace("#", "%40")
signedURL = base_url + "?Expires=" + str(self.expiration) + "&GoogleAccessId=" + self.client_id_email + "&Signature=" + signature_signed
print 'this is the signed URL '
print signedURL
return signedURL
This is called in ios swift with a get post with http. It returns the signed url and it downloads the video to the ios app.
This method here, If i specify the bucketname, the objectname, text/plain as content type, and a couple words for the data, It creates and puts that file into the Google Cloud bucket for me.
def Put(self, path, content_type, data):
"""Performs a PUT request.
Args:
path: The relative API path to access, e.g. '/bucket/object'.
content_type: The content type to assign to the upload.
data: The file data to upload to the new file.
Returns:
An instance of requests.Response containing the HTTP response.
"""
md5_digest = base64.b64encode(md5.new(data).digest())
base_url, query_params = self._MakeUrl('PUT', path, content_type,
md5_digest)
headers = {}
headers['Content-Type'] = content_type
headers['Content-Length'] = str(len(data))
headers['Content-MD5'] = md5_digest
return self.session.put(base_url, params=query_params, headers=headers,
data=data)
What I want to know is one of these two things and nothing else. How do I upload data from a video to this data parameter in my python webapp2.requestHandler from ios? OR How do I get the correct put signed Url to upload video data?
Please do not comment with anything that will not solve this specific question and do not bash me for my methods. Please provide suggests that you feel will specifically help me and nothing else.
There are a few ways to upload images to GCS, and each way works with signed URLs. If the video files are small, your simplest option is to have the users perform a non-resumable upload, which has the same URL signature except that the verb is PUT instead of GET. You'll also need to add the "Content-Type" header to the signature.
Video files can be fairly large, though, so you may prefer to use resumable uploads. These are a bit more complicated but do work with signed URLs as well. You'll need to use the "x-goog-resumable: start" header (and include it in the signature) and set "Content-Length" to 0. You'll get back a response with a Location header containing a new URL. Your client will then use that URL to do the upload. Only the original URL needs to be signed. The client can use the followup URL directly.

How can I respond with JSON to some http GET requests and HTML to others?

I am creating a game in Java, and I need my Java clients to retrieve information from my Ruby on Rails web application in JSON format.
How do I convince my application to respond sometimes with JSON and sometimes with HTML, depending on the nature of the request?
This will always redirect to the homepage, and it seems the JSON part never even runs.
respond_to do |format|
format.html do
redirect_to :root
end
format.json do
render :json => {"test" => "test"}.to_json
end
end
Here is an example of my request:
PrintWriter request = new PrintWriter(socket.getOutputStream());
request.print( "GET " + path + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Authorization: Basic " + encodedAuth + "\r\n" +
"Connection: close\r\n\r\n");
If this cannot (or should not) be done, would it be reasonable for me to create an "API" section of my website that always renders JSON?
You are not requesting json data from your java app. So by default its rendering html.
I believe you should do something like this in your java app
request.addHeader("content-type", "application/json");
Which will make sure you want response in json format.
This will always redirect to the homepage, and it seems the JSON part
never even runs.
If this never runs might be because you are not sending the proper request headers in the java game. Try to get the router in the browser with '.json' at the end of the url and you will be able to retrieve the json response.

How to get the full URL for a request in Hapi

In my hapijs app, given a Request object, how can I find the original, unparsed, unmodified URL?
function getRequestUrl (request) {
return ...; // What goes here?
}
I've found that I can piece it together somewhat from Request.info.host, Request.path, and Request.query, but it lacks the scheme (ie, http vs https), and is a bit of a kludge. Isn't the plain URL available somewhere?
The full URL isn't stored somewhere you can get it. You need to build it yourself from the parts:
const url = request.connection.info.protocol
+ '://'
+ request.info.host
+ request.url.path;
Even though it might seem kludgey, it makes sense if you think about it because there is no original, unparsed, unmodified URL. The HTTP request that goes over the wire doesn't contain the URL as typed into the browser address bar for instance:
GET /hello?a=1&b=2 HTTP/1.1 // request.url.path
Host: localhost:4000 // request.info.host
Connection: keep-alive
Accept-Encoding: gzip, deflate, sdch
...
And you only know the protocol based on whether the hapi server connection is in TLS mode or not (request.connection.info.protocol).
Things to be aware of
If you check either:
request.connection.info.uri or request.server.info.uri
the reported hostname will be the hostname of the actual machine that the server is running on (the output of hostname on *nix). If you want the actual host the person typed in the browser (which might be different) you need to check request.info.host which is parsed from the HTTP request's Host header)
Proxies and X-Forwarded-Proto header
If your request got passed through a proxy(ies)/load balancers/HTTPS terminators, it's possible somewhere along the line HTTPS traffic got terminated and was sent to your server on an HTTP connection, in this case you'll want use the value of the x-forwarded-proto header if it's there:
const url = (request.headers['x-forwarded-proto'] || request.connection.info.protocol)
+ '://'
+ request.info.host
+ request.url.path;
With template strings:
const url = `${request.headers['x-forwarded-proto'] || request.connection.info.protocol}://${request.info.host}${request.url.path}`;
hapi-url solves this exact problem. It is prepared to work with X-Forwarded headers to run behind a proxy. There is also an option to override the automatic resolution if the library is not able to resolve the URL correctly.
I use the following syntax now (using coffee script):
server.on 'response', (data) ->
raw = data.raw.req
url = "#{data.connection.info.protocol}://#{raw.headers.host}#{raw.url}"
console.log "Access to #{url}"
Or as javascript:
​server.on('response', function(data) {
var raw = data.raw.req;
var url = data.connection.info.protocol + "://" +
raw.headers.host + raw.url;
console.log("Access to " + url);
});
That gives you the exact URL like the user requested it.
You can't get the URL. You have to generate it. I'm using this one:
const url = request.headers['x-forwarded-proto'] + '://' +
request.headers.host +
request.url.path;
nowadays there is simply request.url:
https://hapi.dev/api?v=20.2.0#-requesturl

Resources