SvelteKit server-side rendering: creating XML responses - sveltekit

How can one render XML, or other non-HTML output, with SvelteKit? I would like to use SvelteKit to render Google sitemap.xml and RSS feed for my blog.

You would create an endpoint and define a different Content-Type:
// example.xml.js
export async function get() {
const xml =
`<?xml version="1.0" encoding="UTF-8"?>
<data>
<field>This is an example</field>
</data>
`
return {
status: 200,
body: xml,
headers: {
'Content-Type': 'application/xml'
}
}
}
alternative approach
If you want you could create a regular route where you can use Svelte components and hijack the rendering in the handle hook, but this approach is very fragile as simply changing the headers is not enough, the file will still be rendered will other fluff commonly associated with html pages surrounding it (like a body and head tag) so you would have to find a way to cut those out first.
<!-- example.xml.svelte -->
<script>
let value = 0
</script>
<data>
<field>{value}</field>
</data>
// hooks.js
export async function handle({ request, resolve }) {
const response = await resolve(request)
if (request.path.endsWith('.xml')) {
// Set the content type to xml
response.headers["content-type"] = "application/xml"
// remove whitespace
response.body = response.body.replaceAll('\n','')
response.body = response.body.replaceAll('\t','')
// find the body tags if present
const start = response.body.indexOf('<body>')
const stop = response.body.indexOf('</body>')
if (start > 0 && stop > 0) {
// only show the content of the body
response.body = response.body.substring(start + 6, stop)
}
}
return {
...response
}
}

For the reference, I included a full example of generating sitemap.xml based on Stephane Vanraes answer.
Here is routes/sitemap.xml.ts:
// sitemap.xml generation
// This sitemap is manually maintained and page paths included here one by one
const staticPages = [
"",
"about",
"trading-view",
"trading-view/backtesting",
"community",
"trading-view/exchanges",
"trading-view/blockchains",
]
/***
* Sitemap.xml HTTP GET endpoint
*
* For more information see https://stackoverflow.com/a/69523302/315168
*/
export async function get(page) {
// SvelteKit page object does not include protocol, so assume HTTPS
const proto = "https";
// Generate the sitemap.xml file with string fiddling
const fragments = [];
for(let path of staticPages) {
const fullUrl = `${proto}://${page.host}/${path}`;
fragments.push(`<url><loc>${fullUrl}</loc></url>`);
}
// Build the XML for pages
const urlXml = "".concat(...fragments);
// See https://en.wikipedia.org/wiki/Sitemaps
const xml =
`<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
${urlXml}
</urlset>`
return {
status: 200,
body: xml,
headers: {
'Content-Type': 'application/xml'
}
}
}

Related

Dynamic url rendered as a Document, api response. Angular

I'm using Django + Angular, and I have a dynamic url which works for first time when load my product page. I specified as a dynamic url in Django too, so the url look like this "product/home/:productName/:productId". Everything definitely works but as a response when I reload my page, it gets plain API response document. So, I compared the request details and when reload it gets document and angular pollifies not working. I was searching around didn't found anything.
url.py
path('product/home/<str:handle>/<int:id>', ProductGet)
view.py
#csrf_exempt
def ProductGet(request, handle, id):
product = Product.objects.get(id=id)
serializer = ProductSerializer(product, many=False)
return JsonResponse(serializer.data, safe=False)
So, this code works for first time, but then when I reload seems it changes host to Django and I'm getting as a response, my API response.
product-component.ts
ngOnInit(){
this.productService.data$.subscribe(res => this.update = res)
this.card = localStorage.getItem("card")
if(this.card === null){
this.card = null
}else {
this.card = JSON.parse(this.card)
}
this.router.params.subscribe((param:any)=> {
this.route = param.route
if(this.route === "home"){
this.route = {
route: "/",
name: "Основной"
}
}
console.log(param)
this.productService.getProduct(param.id, param.title).subscribe((res: any)=> {
this.product = res
this.selectedOption = res.options[0]
this.selectedOption.selectedOption = res.options[0].values[0]
this.selectedOptionName = res.options[0].values[0]
this.mainImg = this.host + res.image
this.isSale()
this.src1 = this.sanitizer.bypassSecurityTrustResourceUrl(this.product.specs.iframes[0].src)
this.src2 = this.sanitizer.bypassSecurityTrustResourceUrl(this.product.specs.iframes[1].src)
})
})
So, something with browser and framework host where angular didn't understand a request.So the fix.
app.module.ts
import {HashLocationStrategy, Location, LocationStrategy} from '#angular/common';
providers: [
{provide: LocationStrategy, useClass: HashLocationStrategy}
],

Uppy/Shrine: How to retrieve presigned url for video after successful upload (using AWS S3)

I'm using Uppy for file uploads in React, with a Rails API using Shrine.
I'm trying to show a preview for an uploaded video before submitting a form. It's important to emphasize that this is specifically for a video upload, not an image. So the 'thumbnail:generated' event will not apply here.
I can't seem to find any events that uppy provides that returns a cached video preview (like thumbnail:generated does) or anything that passes back a presigned url for the uploaded file (less expected, obviously), so the only option I see is constructing the url manually. Here's what I'm currently trying for that (irrelevant code removed for brevity):
import React, { useEffect, useState } from 'react'
import AwsS3 from '#uppy/aws-s3'
import Uppy from '#uppy/core'
import axios from 'axios'
import { DragDrop } from '#uppy/react'
import { API_BASE } from '../../../api'
const constructParams = (metadata) => ([
`?X-Amz-Algorithm=${metadata['x-amz-algorithm']}`,
`&X-Amz-Credential=${metadata['x-amz-credential']}`,
`&X-Amz-Date=${metadata['x-amz-date']}`,
'&X-Amz-Expires=900',
'&X-Amz-SignedHeaders=host',
`&X-Amz-Signature=${metadata['x-amz-signature']}`,
].join('').replaceAll('/', '%2F'))
const MediaUploader = () => {
const [videoSrc, setVideoSrc] = useState('')
const uppy = new Uppy({
meta: { type: 'content' },
restrictions: {
maxNumberOfFiles: 1
},
autoProceed: true,
})
const getPresigned = async (id, type) => {
const response = await axios.get(`${API_BASE}/s3/params?filename=${id}&type=${type}`)
const { fields, url } = response.data
const params = constructParams(fields)
const presignedUrl = `${url}/${fields.key}${params}`
console.log('presignedUrl from Shrine request data: ', presignedUrl)
setVideoSrc(presignedUrl)
}
useEffect(() => {
uppy
.use(AwsS3, {
id: `AwsS3:${Math.random()}`,
companionUrl: API_BASE,
})
uppy.on('upload-success', (file, _response) => {
const { type, meta } = file
// First attempt to construct presigned URL here
const url = 'https://my-s3-bucket.s3.us-west-1.amazonaws.com'
const params = constructParams(meta)
const presignedUrl = `${url}/${meta.key}${params}`
console.log('presignedUrl from upload-success data: ', presignedUrl)
// Second attempt to construct presigned URL here
const id = meta.key.split(`${process.env.REACT_APP_ENV}/cache/`)[1]
getPresigned(id, type)
})
}, [uppy])
return (
<div className="MediaUploader">
<div className="Uppy__preview__wrapper">
<video
src={videoSrc || ''}
className="Uppy__preview"
controls
/>
</div>
{(!videoSrc || videoSrc === '') && (
<DragDrop
uppy={uppy}
className="UploadForm"
locale={{
strings: {
dropHereOr: 'Drop here or %{browse}',
browse: 'browse',
},
}}
/>
)}
</div>
)
}
export default MediaUploader
Both urls here come back with a SignatureDoesNotMatch error from AWS.
The manual construction of the url comes mainly from constructParams. I have two different implementations of this, the first of which takes the metadata directly from the uploaded file data in the 'upload-success' event, and then just concatenates a string to build the url. The second one uses getPresigned, which makes a request to my API, which points to a generated Shrine path that should return data for a presigned URL. API_BASE simply points to my Rails API. More info on the generated Shrine route here.
It's worth noting that everything works perfectly with the upload process that passes through Shrine, and after submitting the form, I'm able to get a presigned url for the video and play it without issue on the site. So I have no reason to believe Shrine is returning incorrectly signed urls.
I've compared the two presigned urls I'm manually generating in the form, with the url returned from Shrine after uploading. All 3 are identical in structure, but have different signatures. Here are those three urls:
presignedUrl from upload-success data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132613Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=97aefd1ac7f3d42abd2c48fe3ad50b542742ad0717a51528c35f1159bfb15609
presignedUrl from Shrine request data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/023592fb14c63a45f02c1ad89a49e5fd.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132619Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=7171ac72f7db2b8871668f76d96d275aa6c53f71b683bcb6766ac972e549c2b3
presigned url displayed on site after form submission:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132734Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=9ecc98501866f9c5bd460369a7c2ce93901f94c19afa28144e0f99137cdc2aaf
The first two urls come back with SignatureDoesNotMatch, while the third url properly plays the video.
I'm aware the first and third urls have the same file name, while the second url does not. I'm not sure what to make of that, though, but the relevance of this is secondary to me, since that solution was more of a last ditch effort anyway.
I'm not at all attached to the current way I'm doing things. It's just the only solution I could come up with, due to lack of options. If there's a better way of going about this, I'm very open to suggestions.

Format to POST a file to the ImageKit server?

I've successfully built a React UI to select and upload N files. The key part of it is this:
<input type='file' accept='image/*' id='selectFiles' multiple onChange={handleFileChange} />
The selected files are stored in this state variable:
const [fileList, setFileList] = React.useState<FileList>();
I know they're correctly there because I iterate through them and show them in a preview DIV.
Following ImageKit's instructions, I successfully built an Auth endpoint which returns the auth credentials.
Then, within a useEffect I iterated through fileList to upload one photo at a time to the ImageKit server. But even trying just one file, I keep getting a 400 error informing me that the fileName parameter is missing. It definitely is not missing so I suspect that the problem lies with what I'm providing as the file parameter.
Here's the critical code (with some data obscured for privacy reasons) :
const uploadFile = async (file: File) => {
try {
const body = {
file: file,
publicKey: 'my_public_key',
signature: 'imageKit_signature',
expire: 'imageKit_expiry_value',
token: 'imageKit_token',
fileName: 'test123.jpg',
useUniqueFileName: false,
folder: userName,
overwriteFile: false,
};
const response = await axios.post('https://upload.imagekit.io/api/v1/files/upload', body);
console.log(response.status, response.data);
} catch (err) {
console.error(err);
}
};
Might anyone see what I'm doing wrong?
Robert
I solved the problem. If you're going to upload a file to ImageKit using their POST endpoint, you need to explicitly set the headers like this:
const response = await axios.post(
'https://upload.imagekit.io/api/v1/files/upload',
body,
{ headers: {'Content-Type': 'multipart/form-data'} }
);
For those wondering, here's the barebones body:
const body = {
file: base64File,
publicKey: imageKitPublicKey,
signature: authParams?.signature,
expire: authParams?.expire,
token: authParams?.token,
fileName: 'someFilename.ext',
};
But indeed, specifying Content-Type like above will allow you to upload a file to their server.

REACT + DJANGO app, uploading excel files into the backend, Django returns empty dictionary

So I have been trying to upload a excel file from the frontend using a post request to a Django backend, however whatever I do the request[FILES] python dictionary is empty.
Does anyone have an idea of why this would happen?
This is my POST view from the Django views.py file
#api_view(["POST"])
#csrf_exempt
def processFile(request, *args, **kwargs):
data = request.data
print(data.items())
print(str(data.get('file0')))
if len(request.FILES) == 0 :
print("empty files")
print(request.FILES)
return Response("ok")
And now the way I am making the POST request in the frontend.
const fileList = async (actualFiles) => {
var dataForm = new FormData();
console.log(actualFiles)
for(let i=0;i<actualFiles.length;i++) {
const id = 'file' + i;
console.log("appending file" + i)
dataForm.append(id, actualFiles[i])
}
const res = await fetch(`http://localhost:8000/process-file/`,
{
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data; boundary=----somefixedboundary'
},
body : dataForm,
})
const data = await res.json()
console.log("Data:" + data)
}
Worth mentioning: I have been trying for a while thinking the problem is in the request, however in the network tab on the browser I can see that it is all alright.
I am adding the content type and boundary because according to Django documentation if you do not add those the server will not be able to process the data.
Any ideas? Thank you in advance!

How to handle very large file downloads using React js

We are trying to download large files say 1GB or 2 GB but after certain time though the backend still goes on the UI gives error as Failed to fetch for large files.
So how can we handle large file downloads using React js
Please help!
Code as below:
getFile = async (endpoint: string, id: string, params?: any) => {
const response = await fetch(
this.createUrl(endpoint + "/" + id, params),
this.getRequest("get", {
Accept: "application/octet-stream",
"Content-Type": "application/octet-stream",
}),
);
if (response.status === 200) {
return await response.blob();
} else {
throw Error(errorObj.error);
}
};
downloadFile = (filepath: any) => {
this.props.api.getFile(resource, filepath, {}).then((res: any) =>
this.setState(() => {
const url = window.URL.createObjectURL(new Blob([res]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", path.basename(filepath));
document.body.appendChild(link);
link.click();
link.parentNode!.removeChild(link);
toaster.success("Successfully downloaded ");
}),
);
};
Using fetch will buffer the response into memory, and you can't quite expect to buffer 1 to 2 gigabytes in memory. (You could do something clever with IndexedDB like e.g. Mega does, but it's likely not worth it.)
Instead of fetching the data from an URL (let's call it URL A) and creating an Object URL from the content blob to put in a download link you click, simply put URL A in the download link.
If the endpoint at URL A requires some authentication or similar, you will need to change that to something that can be encoded into query parameters; maybe a token with a signature akin to what AWS S3 does with presigned URLs.

Resources