-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add passing an URL and query string params to resize an image? #127
Comments
@ndrean nice. 👌 {"h":600,"w":600,"url":"https://dwyl-imgup.s3.eu-west-3.amazonaws.com/6E70A71E.webp",
"h_origin":800,"init_size":56172,"w_origin":800,"new_size":20980} https://dwyl-imgup.s3.eu-west-3.amazonaws.com/6E70A71E.webp Each change in width/height results in a new image: {"h":200,"w":200,"url":"https://dwyl-imgup.s3.eu-west-3.amazonaws.com/1BACCDFF.webp",
"h_origin":800,"init_size":55149,"w_origin":800,"new_size":3640} https://dwyl-imgup.s3.eu-west-3.amazonaws.com/1BACCDFF.webp This is a perfectly valid use case. Especially the use of As outlined in #91 (comment) I still prefer the idea of having a single version of an image in storage |
Thanks for your evaluation. I have to better understand how you compute a resized image and transfer the resized image to the CDN, and then how do you call a resized image when used. EDIT: I think that I can set up a DNS on Cloudfare, and keep the app hosted on Fly.io. Then instead of using S3, I can use R2. Then I think I can just add this domain as Cloudfare will cache the files. Now how to use R2 instead of S3. Probably client -> R2 must not be too difficult (?), but this would be only for the original image. Since I need to transform the images, I need client -> elixir-app -> R2, and able to interact with R2. The whole R2 API to explore. |
I added another functionality: a POST endpoint. From a client - the browser -, you want to upload multiple files and you get a JSON response with a simple @nelsonic The code below might interest you as this is not standard. The "secret" is to build your own multipart parser that will effectively parse a FormData. The idea is to exchange the key used as the input for a file in the FormData (it will the same, namely the "name" attribute of the input) to a new indexed one that you create. That's it. The cool part is that you are "almsot" independant on how the front-end coded it, just a FormData (see the HTML example below, 2 lines of JS). I still request to use "w" if you want a specific resize. defmodule Plug.Parsers.FD_MULTIPART do
@multipart Plug.Parsers.MULTIPART
def init(opts) do
opts
end
def parse(conn, "multipart", subtype, headers, opts) do
length = System.fetch_env!("UPLOAD_LIMIT") |> String.to_integer()
opts = @multipart.init([length: length] ++ opts)
@multipart.parse(conn, "multipart", subtype, headers, opts)
end
def parse(conn, _type, _subtype, _headers, _opts) do
{:next, conn}
end
def multipart_to_params(parts, conn) do
case filter_content_type(parts) do
nil ->
{:ok, %{}, conn}
new_parts ->
acc =
for {name, _headers, body} <- Enum.reverse(new_parts),
reduce: Plug.Conn.Query.decode_init() do
acc -> Plug.Conn.Query.decode_each({name, body}, acc)
end
{:ok, Plug.Conn.Query.decode_done(acc, []), conn}
end
end
def filter_content_type(parts) do
filtered =
parts
|> Enum.filter(fn
{_, [{"content-type", _}, {"content-disposition", _}], %Plug.Upload{}} = part ->
part
{_, [_], _} ->
nil
end)
l = length(filtered)
case l do
# user pressed enter without any files => do nothing
0 ->
nil
_ ->
# get the none "content-type" inputs
other = Enum.filter(parts, fn elt -> !Enum.member?(filtered, elt) end)
# get the key used to name the files
key = elem(hd(filtered), 0)
# build a new list of keys
new_keys = keys = Enum.map(1..l, fn i -> key <> "#{i}" end)
# and exchange the "old" key to the new indexed one. The keys will be unique this way.
f =
Enum.zip_reduce([filtered, new_keys], [], fn elts, acc ->
[{_, headers, content}, new_key] = elts
[{new_key, headers, content} | acc]
end)
# rebuild the "parts"
f ++ other
end
end
end To use this beast, "just" add to your API pipeline: #router
pipeline :api do
plug :accepts, ["json"]
plug CORSPlug,
origin: ["*"]
plug Plug.Parsers,
parsers: [:urlencoded, :my_multipart, :json],
pass: ["image/jpg", "image/png", "image/webp", "iamge/jpeg"],
json_decoder: Jason,
multipart_to_params: {Plug.Parsers.FD_MULTIPART, :multipart_to_params, []},
body_reader: {Plug.Parsers.FD_MULTIPART, :read_body, []}
end
scope "/api", UpImgWeb do
pipe_through :api
get "/", ApiController, :create
post "/", ApiController, :handle
end To test this quickly, it is easy: create from the code an "index.html" and <html>
<body>
<form
id="f"
action="https://up-image.fly.dev/api"
method="POST"
enctype="multipart/form-data"
>
<input type="file" name="file" multiple />
<input type="number" name= "w"/>
<input type="checkbox" name="thumb"/>
<button form="f">Upload</button>
</form>
<script>
const form = ({ method, action } = document.forms[0]);
form.onsubmit = async (e) => {
e.preventDefault();
return fetch(action, { method, body: new FormData(form) })
.then((r) => r.json())
.catch(console.log);
};
</script>
</body>
</html> You will get a JSON response All async process to S3, thanks to Elixir. It was a real pleasure to code this. |
I added today the following functionality to my toy fork: if a picture is served, you pass a GET request to the endpoint with the URL in the query string and get back a link to a resized WEBP picture from S3. I found some pictures that were served and wanted to use them.
I choose WEBP format to limit traffic on S3 and bandwidth usage on mobile (and canIUse is :ok).
To run in a Livebook:
or
It will deliver a
json
reply with the URL of the new file in S3.Let me know if you find some interest.
Endpoint: (normally works 😀) https://up-image.fly.dev/api
The text was updated successfully, but these errors were encountered: