https://habr.com/ru/post/491548/Доброго времени суток.
Разбор полетов провожу на
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
- Простая реализация на хуках