Доброго времени суток.
Разбор полетов провожу на Reactjs (сторона клиента) и Nodejs (сторона сервера).
Недавно в моем маленьком проекте встал вопрос, как легко и просто можно обмениваться изображениями по типу клиент — сервер.
Сегодня мы научимся отправлять бинарные данные (конкретно изображения ) со стороны клиента и обрабатывать их на сервере. Добро пожаловать в под кат.
Если ваше web-приложение — это соц.сеть или мессенджер или что-то подобное, то вам непременно придется работать с бинарными данными, в большинстве случаев — это обработка изображений. Об этом и пойдет речь.
Чтобы иметь общее представление, я буду толковать последовательно.
Ознакомимся со следующим
- Двоичная система счисления и десятичная система счисления
- Кодовая страница: ASCII, Unicode
- Бит и байт
- Формат hex
- Двоичный файл
- Буффер и потоки nodejs
- ArrayBuffer и Uint8Array
- FileReader
Двоичная система счисления и десятичная система счисления.
Это число, которое записывается с помощью двух символов: единиц и нулей.
Простым языком — это 2 (основание) в степени n (количество разрядов), например число 10001 имеет 5 разрядов:
4 разряд = 1; 2^4 = 16;
3 разряд = 0;
2 разряд = 0;
1 разряд = 0;
0 разряд = 1; 2^0 = 1;
1-true — производим расчет;
0-false — не производим расчет;
В итоге получаем 16 + 1 = 17;
Обратной операцией компьютер представляет данные в двоичном виде.
const num = 17; // десятичное число
const binNum = num.toString(2); // преобразование в двоичный вид
const initNum = parseInt(binNum, 2); // обратная операция
Десятичная система счисления — это число, которое записывается с помощью 10 символов, от 0 — 9. Мы используем их повседневно.
Дело в том, что компьютер живет своим представлением информации. Если мы пишем на родном языке или считаем в десятичной системе счисления, то для компьютера все это двоичное представление. Вы спросите: “Что значит двоичное представление?”. Как говорила моя учительница английского, “как слышится, так и пишется”. Компьютер представляет файлы, содержащие текст или иное, или какие-либо вычисления в двоичную систему счисления — это и есть двоичное представление. Языком машины число «17» является — 10001.
Кодовая страница: ASCII, Unicode
Возникает вопрос. Понятно с числами, но как же текст? Как машина будет преобразовывать текст в двоичный вид? Все просто. Для этого существуют кодировки типа ASCII или Unicode. Это простая таблица ( кодовая страница ), где имеется сопоставление нашему символу число, например символ “S” — это число 53 по таблице ASCII, далее компьютер представит число 53 в двоичном виде — 110101.
Что касаемо Unicode — это мощный стандарт, включающий в себя кодировку UTF-8, которая имеет большой спрос в web-пространстве и unix подобных системах. Принцип работы общий.
Бит и байт
Бит — это единица измерения информации компьютером. А мы уже знаем как компьютер представляет информацию. Да! Вы правы. Один бит равен 1 или 0. Для компьютера 1 или 0 — это что-то вроде транзистора, true или false. Теперь понять что такое байт совсем не трудно — это 8 битов, его еще называют октет ( типа состоит из 8 ).
Формат hex
Переходим к “hex” формату. Мы говорили о двоичной системе счисления, десятичной системе, помимо прочего существует и шестнадцатиричная система счисления, верстальщики поймут о чем я. Это число, которое записывается с помощью 16 символов, от 0 — 9 и от a-f. Навивается вопрос: “Зачем так все усложнять?”. Так как единицей памяти является 8-битный байт, все же значение его удобнее записывать двумя шестнадцатиричными цифрами. Также в стандарте Юникода номер символа принято записывать в шестнадцатиричном виде, используя не менее 4 цифр.
Данный формат работает следующим образом: в случае с 8 битным байтом, он делит 8 битов по полам, то есть 10101011 — 1010 | 1011, после преобразует каждую часть соответственно. 1010 — a, 1011 — b;
const num = 196; // десятичное число
const hexNum = num.toString(16); // преобразование в hex вид
const initNum = parseInt(hexNum, 16); // преобразование в десятичный вид - обратная операция
Двоичный файл
Переходим к следующему пункту, его еще называют бинарным файлом — это массив байтов. Почему двоичный? Байты состоят из битов, то есть символов двоичной системы счисления, отсюда и название двоичный. Под категорию двоичный файл подходят абсолютно все файлы, то есть файлы содержащие текст, изображения — все это двоичные файлы, отсюда двоичные данные, отсюда бинарные данные.
Нередко бинарные данные выводятся символами кодовой страницы ASCII, а также в формате hex, в частности изображения. Приведу пример. У нас есть следующее изображение:
Его частичная визуализация представляется в виде hex-agent-smith
Правая часть символов является символами таблицы ASCII, левая же часть как раз и есть hex формат, преобразованный от двоичного кода.
ArrayBuffer и Uint8Array
ArrayBuffer — объект для работы с бинарными данными на javascript. Фиксирует длину памяти для работы с бинарником. Например: new ArrayBuffer(256) — создаст массивоподобный объект размером в 256 байт, не больше — не меньше. На каждый байт приходится конкретная информация. ArrayBuffer не является массивом и методы массива к нему не применимы. Вы зададите вопрос, как же его использовать? Объект Uint8Array дает представление ArrayBuffer в беззнаковое 8-битное число, то есть от 0 — 255.
FileReader
FileReader? Это конструктор по созданию следующего:
<input type="file">
Что дальше?
Теперь рассмотрим прекрасный пример, демонстрирующий обработку изображения:
- Модуль ReactJS
import React, {useState, useRef, useCallback} from 'react'; import axios from 'axios'; export const UPLOAD_AVATAR = 'http://localhost:8080/api/upload_avatar'; function App() { // определим изменяемый ref для объекта FileReader const fileRef = useRef(null); const [ loading, setLoading ] = useState(false); const handleSubmit = useCallback( event => { event.preventDefault(); const fetchData = async (uint8Array) => { try { const response = await axios({ method: 'post', url: UPLOAD_AVATAR, data: [...uint8Array] // не отправляем в JSON, размер изображения увеличится }); setLoading(false); console.log(response); } catch (e) { console.error(e.message, 'function handleSubmit') } }; if(!fileRef.current) return void null; const reader = new FileReader(); reader.onloadend = () => { const uint8Array = new Uint8Array(reader.result); setLoading(true); fetchData(uint8Array); }; // рекомендованный метод reader.readAsArrayBuffer(fileRef.current[0]); // метод reader.readAsBinaryString(fileRef.current[0]) // согласно MDN, // уже был однажды удален из File API specification, // но после его вернули // в использование, но все же рекомендуют // использовать readAsArrayBuffer // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString }, []); const nodeDom = ( <form onSubmit={handleSubmit}> <div> <input onChange={e => fileRef.current = e.target.files} accept="image/*" type="file" id="button-file" /> </div> <button type="submit">{loading ? 'Сохраняю...' : 'Сохранить'}</button> </form> ); return nodeDom } export default App;
- Модуль ExpressJS
const express = require("express"); const cors = require("cors"); const crypto = require('crypto'); const fs = require('fs'); const PORT = 8080; const app = express(); app.use(express.static("public")); app.use(express.json({ limit: "50mb" })); app.use(cors()); app.post("/api/upload_avatar", (req, res) => { console.log(req.body); const randomString = crypto.randomBytes(5).toString('hex'); const stream = fs.createWriteStream(`./public/images/${randomString}.png`); stream.on('finish', function () { console.log('file has been written'); res.end('file has been written'); }); stream.write(Buffer.from(req.body), 'utf-8'); stream.end(); }); app.listen(PORT, () => console.log(`Server running at ${PORT}`));
Итог:
- Для чего нужны кодировки
- Общее представление бинарных данных
- Рекомендуемый способ отправки байтов
- Объект буфера
- Объект FileReader
- Простая реализация на хуках
Автор: Имран Гаджиев