Is it possible to change headers on an S3 object without downloading the entire object? - amazon-s3

I've uploaded a bunch of images to Amazon S3, and now want to add a Cache-Control header to them.
Can the header be updated without downloading the entire image? If so, how?

It's beta functionality, but you can specify new meta data when you copy an object. Specify the same source and destination for the copy, and this has the effect of just updating the meta data on your object.
PUT /myObject HTTP/1.1
Host: mybucket.s3.amazonaws.com
x-amz-copy-source: /mybucket/myObject
x-amz-metadata-directive: REPLACE
x-amz-meta-myKey: newValue

This is out of beta and is available by doing a put command and copying the object as documented here. It is also available in their SDK's. For example with C#:
var s3Client = new AmazonS3Client("publicKey", "privateKey");
var copyRequest = new CopyObjectRequest()
.WithDirective(S3MetadataDirective.REPLACE)
.WithSourceBucket("bucketName")
.WithSourceKey("fileName")
.WithDestinationBucket("bucketName")
.WithDestinationKey("fileName)
.WithMetaData(new NameValueCollection { { "x-amz-meta-yourKey", "your-value }, { "x-amz-your-otherKey", "your-value" } });
var copyResponse = s3Client.CopyObject(copyRequest);

This is how you do it with AWS SDK for PHP 2:
<?php
require 'vendor/autoload.php';
use Aws\Common\Aws;
use Aws\S3\Enum\CannedAcl;
use Aws\S3\Exception\S3Exception;
const MONTH = 2592000;
// Instantiate an S3 client
$s3 = Aws::factory('config.php')->get('s3');
// Settings
$bucketName = 'example.com';
$objectKey = 'image.jpg';
$maxAge = MONTH;
$contentType = 'image/jpeg';
try {
$o = $s3->copyObject(array(
'Bucket' => $bucketName,
'Key' => $objectKey,
'CopySource' => $bucketName . '/'. $objectKey,
'MetadataDirective' => 'REPLACE',
'ACL' => CannedAcl::PUBLIC_READ,
'command.headers' => array(
'Cache-Control' => 'public,max-age=' . $maxAge,
'Content-Type' => $contentType
)
));
// print_r($o->ETag);
} catch (Exception $e) {
echo $objectKey . ': ' . $e->getMessage() . PHP_EOL;
}
?>

with the amazon aws-sdk, Doing a copy_object with extra headers seems to do the trick for setting caching control headers for an existing S3 Object.
=====================x===============================================
<?php
error_reporting(-1);
require_once 'sdk.class.php';
// UPLOAD FILES TO S3
// Instantiate the AmazonS3 class
$options = array("key" => "aws-key" , "secret" => "aws-secret") ;
$s3 = new AmazonS3($options);
$bucket = "bucket.3mik.com" ;
$exists = $s3->if_bucket_exists($bucket);
if(!$exists) {
trigger_error("S3 bucket does not exists \n" , E_USER_ERROR);
}
$name = "cows-and-aliens.jpg" ;
echo " change headers for $name \n" ;
$source = array("bucket" => $bucket, "filename" => $name);
$dest = array("bucket" => $bucket, "filename" => $name);
//caching headers
$offset = 3600*24*365;
$expiresOn = gmdate('D, d M Y H:i:s \G\M\T', time() + $offset);
$headers = array('Expires' => $expiresOn, 'Cache-Control' => 'public, max-age=31536000');
$meta = array('acl' => AmazonS3::ACL_PUBLIC, 'headers' => $headers);
$response = $s3->copy_object($source,$dest,$meta);
if($response->isOk()){
printf("copy object done \n" );
}else {
printf("Error in copy object \n" );
}
?>
=======================x================================================

In Java, try this
S3Object s3Object = amazonS3Client.getObject(bucketName, fileKey);
ObjectMetadata metadata = s3Object.getObjectMetadata();
Map customMetaData = new HashMap();
customMetaData.put("yourKey", "updateValue");
customMetaData.put("otherKey", "newValue");
metadata.setUserMetadata(customMetaData);
amazonS3Client.putObject(new PutObjectRequest(bucketName, fileId, s3Object.getObjectContent(), metadata));
You can also try copy object. Here metadata will not copy while copying an Object.
You have to get metadata of original and set to copy request.
This method is more recommended to insert or update metadata of an Amazon S3 object
ObjectMetadata metadata = amazonS3Client.getObjectMetadata(bucketName, fileKey);
ObjectMetadata metadataCopy = new ObjectMetadata();
metadataCopy.addUserMetadata("yourKey", "updateValue");
metadataCopy.addUserMetadata("otherKey", "newValue");
metadataCopy.addUserMetadata("existingKey", metadata.getUserMetaDataOf("existingValue"));
CopyObjectRequest request = new CopyObjectRequest(bucketName, fileKey, bucketName, fileKey)
.withSourceBucketName(bucketName)
.withSourceKey(fileKey)
.withNewObjectMetadata(metadataCopy);
amazonS3Client.copyObject(request);

Here is a helping code in Python.
import boto
one_year = 3600*24*365
cckey = 'cache-control'
s3_connection = S3Connection()
bucket_name = 'my_bucket'
bucket = s3_connection.get_bucket(bucket_name validate=False)
for key in bucket:
key_name = key.key
if key.size == 0: # continue on directories
continue
# Get key object
key = bucket.get_key(key_name)
if key.cache_control is not None:
print("Exists")
continue
cache_time = one_year
#set metdata
key.set_metadata(name=cckey, value = ('max-age=%d, public' % (cache_time)))
key.set_metadata(name='content-type', value = key.content_type)
# Copy the same key
key2 = key.copy(key.bucket.name, key.name, key.metadata, preserve_acl=True)
continue
Explanation: Code adds new metadata to the existing key and then copies the same file.

Related

pion/laravel-chunk-upload Laravel not working with large files

I am using resumable.js and Laravel Chunk for uploading large files. It works for small files but didn't work for > 500mb. Chunk is part the file but it didn't re-compile the file to a given directory.
Namespaces
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Pion\Laravel\ChunkUpload\Handler\AbstractHandler;
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
use Illuminate\Support\Facades\Storage;
Controller
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
if (!$receiver->isUploaded()) {
// file not uploaded
}
$fileReceived = $receiver->receive(); // receive file
if ($fileReceived->isFinished()) { // file uploading is complete / all chunks are uploaded
$file = $fileReceived->getFile(); // get file
$extension = $file->getClientOriginalExtension();
$fileName = str_replace('.'.$extension, '', $file->getClientOriginalName()); //file name without extenstion
$fileName .= '_' . md5(time()) . '.' . $extension; // a unique file name
$disk = Storage::disk('new');
$path = $disk->putFileAs('resources/techpacks', $file, $fileName);
// delete chunked file
unlink($file->getPathname());
// return [
// 'path' => asset('storage/' . $path),
// 'filename' => $fileName
// ];
}
Resumable
let browseFile = $('#browseFile');
let resumable = new Resumable({
target: "{{ url('admin/techpack/insert') }}",
query:{_token:'{{ csrf_token() }}'} ,// CSRF token
fileType: ['zip'],
chunkSize: 10*1024*1024, // default is 1*1024*1024, this should be less than your maximum limit in php.ini
headers: {
'Accept' : 'application/json'
},
testChunks: false,
throttleProgressCallbacks: 1,
});
resumable.assignBrowse(browseFile[0]);
resumable.on('fileAdded', function (file) { // trigger when file picked
showProgress();
resumable.upload() // to actually start uploading.
});
resumable.on('fileProgress', function (file) { // trigger when file progress update
updateProgress(Math.floor(file.progress() * 100));
});
resumable.on('fileSuccess', function (file, response) { // trigger when file upload complete
// response = JSON.parse(response)
console.log(response)
});
resumable.on('fileError', function (file, response) { // trigger when there is any error
alert('file uploading error.')
});
let progress = $('.progress');
function showProgress() {
progress.find('.progress-bar').css('width', '0%');
progress.find('.progress-bar').html('0%');
progress.find('.progress-bar').removeClass('bg-success');
progress.show();
}
function updateProgress(value) {
progress.find('.progress-bar').css('width', `${value}%`)
progress.find('.progress-bar').html(`${value}%`)
}
function hideProgress() {
progress.hide();
}
Chunk Folder
After Re-Building
Added to Target Folder
But I can't open the final file. Please guide me.
Windows Error
Resumable Error

How can I validate that a website sends a 2XX response using selenium in perl?

as far as I understand, there is no way in selenium to get the response code of the website. How can I work it around to know if a website has sent me an error or exception without having to expect an element in the site and wait for it until it times out?
use strict;
use warnings;
use Selenium::Chrome;
use Selenium::Waiter qw/wait_until/;
my $chrome_driver_path = "./../../tools/drivers/chromedriver.exe";
my $driver;
my %settings = (
'binary' => $chrome_driver_path,
);
$driver = Selenium::Chrome->new(%settings);
print("Getting stackoverflow\n");
wait_until{$driver->get("https://www.stackoverflow.com")};
validate_site($driver);
print("Getting unexistent url of stackoverflow\n");
wait_until{$driver->get("https://www.stackoverflow.com/this-does-not-exists-and-returns-404")};
validate_site($driver);
sleep(20);
$driver->shutdown_binary;
sub validate_site{
my ($driver) = #_;
#if ($driver->something) {
# print("Looks good)\n");
#}else{
# warn("Error\n");
# }
}
Expected result:
Getting stackoverflow:
Looks good
Getting unexistent url of stackoverflow:
Error
PD: I want to use selenium because I´m working in websites with javascript and storing cookies through different views, this is just an example to illustrate the problem that could be solved with a curl, but is not the case in my project.
Based on solution from your previous comment here's another solution.
Here extra_capabilities are used to enable more logging (please note I added additional package). This will work in version 1.38 of Selenium::Remote::Driver that was released just recently, so you will need to update you packages if you haven't done so yet. This solution does not require falling back to WD2.
use strict;
use warnings;
use Selenium::Chrome;
use Selenium::Waiter qw/wait_until/;
use JSON;
my $chrome_driver_path = "./../../tools/drivers/chromedriver.exe";
my $driver;
my %settings = (
'binary' => $chrome_driver_path,
'extra_capabilities' =>{
'goog:loggingPrefs' => {
'performance' => 'ALL',
},
'goog:chromeOptions' => {
'perfLoggingPrefs' => {
'traceCategories' => 'performance',
},
},
}
);
$driver = Selenium::Chrome->new(%settings);
print("Getting stackoverflow\n");
wait_until{$driver->get("https://www.stackoverflow.com")};
validate_site($driver);
print("Getting unexistent url of stackoverflow\n");
wait_until{$driver->get("https://www.stackoverflow.com/this-does-not-exists-and-returns-404")};
validate_site($driver);
#sleep(20);
$driver->shutdown_binary;
sub validate_site{
my ($driver) = #_;
my $logs = $driver->get_log('performance');
my #responses = grep {$_->{'message'} =~ /"Network\.responseReceived"/ } #$logs;
my #stat = grep {$_->{'message'} =~ $driver->get_current_url() } #responses;
my $json= decode_json $stat[0]->{'message'};
my $status = $json->{'message'}->{'params'}->{'response'}->{'status'};
if ($status==200) {
print("Looks good)\n");
}else{
warn("Error\n");
}
}
In version 1.37 you would have to fallback to WD2 as goog:loggingPrefs capability was not supoprted:
use strict;
use warnings;
use Selenium::Chrome;
use Selenium::Waiter qw/wait_until/;
use JSON;
my $chrome_driver_path = "./../../tools/drivers/chromedriver.exe";
my $driver;
my %settings = (
'binary' => $chrome_driver_path,
'extra_capabilities' =>{
'loggingPrefs' => {
#'browser' => 'ALL',
#'driver' => 'ALL',
'performance' => 'ALL'
},
'perfLoggingPrefs' => {
'traceCategories' => 'performance'
},
}
);
$Selenium::Remote::Driver::FORCE_WD2=1;
$driver = Selenium::Chrome->new(%settings);
print("Getting stackoverflow\n");
wait_until{$driver->get("https://www.stackoverflow.com")};
validate_site($driver);
print("Getting unexistent url of stackoverflow\n");
wait_until{$driver->get("https://www.stackoverflow.com/this-does-not-exists-and-returns-404")};
validate_site($driver);
#sleep(20);
$driver->shutdown_binary;
sub validate_site{
my ($driver) = #_;
my $logs = $driver->get_log('performance');
my #responses = grep {$_->{'message'} =~ /"Network\.responseReceived"/ } #$logs;
my #stat = grep {$_->{'message'} =~ $driver->get_current_url() } #responses;
my $json= decode_json $stat[0]->{'message'};
my $status = $json->{'message'}->{'params'}->{'response'}->{'status'};
if ($status==200) {
print("Looks good)\n");
}else{
warn("Error\n");
}
}
As you mentioned, WebDriver does not expose http response codes and it is suggested to use proxy if you really need it .
If you do not want to wait for the element too long, you could reduce implicit timeouts in validate_site and look for reliable element eg:
sub validate_site{
my ($driver) = #_;
my $implicit=$driver->get_timeouts()->{implicit};# get current value
$driver->set_implicit_wait_timeout(0);# set it to 0
my #elem = $driver->find_elements(".py128","css");#this 'reliable element' it's present on https://www.stackoverflow.com but not on 404 page
if (#elem) {
print("Looks good)\n");
}else{
warn("Error\n");
}
$driver->set_implicit_wait_timeout($implicit);# restore original value
}
or if you really want to workaround it and don't mind duplicating requests you could try https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
sub validate_site{
my ($driver) = #_;
my $script = q{
let page_url=window.document.URL;
let resp = await fetch(page_url);
return resp.status;
};
my $status = $driver->execute_script($script);
if ($status==200) {
print("Looks good)\n");
}else{
warn("Error\n");
}
}
if you don't need the body (saves you response size) then you can request only headers by adding HEAD method (instead of default GET)
let resp = await fetch(page_url,{method:"HEAD"});

Laravel 5.8 rest client how to save api token in the .env

I should like to insert the return token from api into the .env in when after i want pass it header in
<!-- language: php -->
class GuzzleController extends Controller
{
public function getToken()
{
$client = new Client();
$request = $client->request('POST', 'http://192.168.53.27:1996/api/login/',
[
'form_params' => [
'user_name' => 'userName',
'password' => 'Passs',
]
]);
return json_decode((string)$request->getBody(), true);
}
}
As same question has been answere here;
This method should save new value to your .env file
private function setEnvironmentValue($envKey, $envValue)
{
$envFile = app()->environmentFilePath();
$str = file_get_contents($envFile);
$str .= "\n"; // In case the searched variable is in the last line without \n
$keyPosition = strpos($str, "{$envKey}=");
$endOfLinePosition = strpos($str, PHP_EOL, $keyPosition);
$oldLine = substr($str, $keyPosition, $endOfLinePosition - $keyPosition);
$str = str_replace($oldLine, "{$envKey}={$envValue}", $str);
$str = substr($str, 0, -1);
$fp = fopen($envFile, 'w');
fwrite($fp, $str);
fclose($fp);
}
usage
$this->setEnvironmentValue('DEPLOY_SERVER', 'forge#122.11.244.10');

urlopen method in Perl 6?

I'm translating a Python module to Perl 6, but can't find a method called urlopen, which could accept data:
from six.moves.urllib import request
req = request.Request(url, headers=headers)
if headers.get('Content-Type') == 'application/x-www-form-urlencoded':
data = oauth_query(args, via='quote_plus', safe='').encode()
elif 'form-data' in headers.get('Content-Type', ''): # multipart/form-data
data = args['form-data']
else:
data = None
resp = request.urlopen(req, data=data)
resp.json = lambda: json.loads(resp.read().decode() or '""')
return resp
oauth_query is a method that return a sorted string:
def oauth_query(args, via='quote', safe='~'):
return '&'.join('%s=%s' % (k, oauth_escape(v, via, safe)) for k, v in sorted(args.items()))
I translate the above code to Perl 6:
use WWW;
my $data = "";
if %headers{'Content-Type'} eq 'application/x-www-form-urlencoded' {
$data = oauth_query(%args);
} elsif %headers{'Content-Type'}.contains('form-data') {
$data = %args{'form-data'};
} else {
$data = Any;
}
my $res = get $url, |%headers; # but without data that contains Content-Type, it will
# Died with HTTP::MediaType::X::MediaTypeParser::IllegalMediaType
I want to return a resp as in Python. Any help is welcome!
I have reduced the program to the bare minimum; you will still have to take care of headers and the OAuth query, but this works
use WWW;
sub MAIN( :$have-data = 0 ) {
my $url='https://jsonplaceholder.typicode.com/posts/';
my %args=%(form-data => "userId=1&id=2");
my $data = "";
if $have-data {
$data = %args{'form-data'};
}
my $res;
if $data {
$res = post $url, $data;
} else {
$res= get $url~'1';
}
say $res;
}
Baseline is that urlopen in Python does get or post depending on whether there is data or not. In this case, I use a simple if for that purpose, since WWW is quite barebones and does not support that. I am using also a mock REST interface, so I have actually to change the URL depending on the data, which is also dummy data. You can call the program either with no argument or with
perl6 urlopen.p6 --have-data=1
and the mock server will return... something. It would be great if you contributed a module with a (somewhat) higher level than WWW, or to WWW itself. Hope this solves (kinda) your problem.
use Cro::HTTP::Client;
my $resp;
my $data = "";
if (%headers{'content-type'} // '') eq self.form_urlencoded {
$data = oauth_query(%args);
} elsif (%headers{'content-type'} // '').contains('form-data') { # multipart/form-data
$data = %args{'form-data'};
} else {
$data = "";
}
my $client = Cro::HTTP::Client.new(headers => |%headers);
if $data {
$resp = await $client.post: $url, body => |%args;
} else {
$resp = await $client.get: $url;
}
return $resp;

My file not in uploads directory after upload was successful

I try to upload file using Yii2 file upload and the file path was successful saved to the database but the file was not saved to the directory I specify.. below is my code..
<?php
namespace backend\models;
use yii\base\Model;
use yii\web\UploadedFile;
use yii\Validators\FileValidator;
use Yii;
class UploadForm extends Model
{
/**
* #var UploadedFile
*/
public $image;
public $randomCharacter;
public function rules(){
return[
[['image'], 'file', 'skipOnEmpty' => false, 'extensions'=> 'png, jpg,jpeg'],
];
}
public function upload(){
$path = \Yii::getAlias("#backend/web/uploads/");
$randomString = "";
$length = 10;
$character = "QWERTYUIOPLKJHGFDSAZXCVBNMlkjhgfdsaqwertpoiuyzxcvbnm1098723456";
$randomString = substr(str_shuffle($character),0,$length);
$this->randomCharacter = $randomString;
if ($this->validate()){
$this->image->saveAs($path .$this->randomCharacter .'.'.$this->image->extension);
//$this->image->saveAs(\Yii::getAlias("#backend/web/uploads/{$randomString}.{$this->image->extension}"));
return true;
}else{
return false;
}
}
}
The controller to create the fileupload
namespace backend\controllers;
use Yii;
use backend\models\Product;
use backend\models\ProductSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use backend\models\UploadForm;
use yii\web\UploadedFile;
public function actionCreate()
{
$addd_at = time();
$model = new Product();
$upload = new UploadForm();
if($model->load(Yii::$app->request->post())){
//get instance of the uploaded file
$model->image = UploadedFile::getInstance($model, 'image');
$upload->upload();
$model->added_at = $addd_at;
$model->image = 'uploads/' .$upload->randomCharacter .'.'.$model->image->extension;
$model->save();
return $this->redirect(['view', 'product_id' => $model->product_id]);
} else{
return $this->render('create', [
'model' => $model,
]);
}
}
Does it throw any errors?
This is propably permission issue. Try changing the "uploads" directory permission to 777 (for test only).
You load your Product ($model) with form data.
if($model->load(Yii::$app->request->post()))
But Uploadform ($upload) never gets filled in your script. Consequently, $upload->image will be empty.
Since you declare 'skipOnEmpty' => false in the file validator of the UploadForm rules, the validation on $upload will fail.
That is why your if statement in the comments above (if($upload->upload()) doesn't save $model data.
I don't see why you would need another model to serve this purpose. It only complicates things, so I assume its because you copied it from a tutorial. To fix and make things more simple, just do the following things:
Add property to Product model
public $image;
Add image rule to Product model
[['image'], 'file', 'skipOnEmpty' => false, 'extensions'=> 'png, jpg,jpeg'],
Adjust controller create action
public function actionCreate()
{
$model = new Product();
if($model->load(Yii::$app->request->post()) && $model->validate()) {
// load image
$image = UploadedFile::getInstance($model, 'image');
// generate random filename
$rand = Yii::$app->security->generateRandomString(10);
// define upload path
$path = 'uploads/' . $rand . '.' . $image->extension;
// store image to server
$image->saveAs('#webroot/' . $path);
$model->added_at = time();
$model->image = $path;
if($model->save()) {
return $this->redirect(['view', 'product_id' => $model->product_id]);
}
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
Something like this should do the trick.
Your UploadForm class is already on Backend so on function upload of UploadForm Class it should be like this:
Change this line:
$path = \Yii::getAlias("#backend/web/uploads/");
to this:
$path = \Yii::getAlias("uploads")."/";