Skip to content

Commit e66db07

Browse files
authored
Merge pull request #1 from nipoks/lab1
Lab1
2 parents ea3ad69 + 84ee771 commit e66db07

24 files changed

+664
-16
lines changed

.DS_Store

-6 KB
Binary file not shown.

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea
2-
2+
.DS_Store
3+
.env

README.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Hash Cracking Worker System
2+
3+
Этот проект представляет собой систему для параллельного поиска строк, которые соответствуют заданному MD5 хэшу. Процесс разбивается на несколько воркеров, каждый из которых обрабатывает часть возможных строк, и результаты отправляются менеджеру.
4+
5+
## Структура проекта
6+
![Пример работы](./img/plan.jpg)
7+
8+
Проект состоит из нескольких сервисов:
9+
10+
- **manager**: Сервис, который управляет распределением задач и сбором результатов.
11+
- **worker**: Рабочие сервисы, которые выполняют фактическую работу по вычислению строк и хэшированию.
12+
13+
### Технологии:
14+
- **Node.js** - основной язык для разработки.
15+
- **Express.js** - веб-фреймворк для создания API.
16+
- **Worker Threads** - для выполнения вычислений в отдельных потоках.
17+
- **Axios** - для отправки HTTP-запросов.
18+
- **MD5** - для хэширования строк.
19+
- **SharedMap** - для сохранения процента выполнения задачи из разных потоков (https://www.npmjs.com/package/sharedmap).
20+
21+
## Установка
22+
23+
Для того, чтобы запустить проект, нужно:
24+
25+
1. Клонировать репозиторий:
26+
```
27+
git clone https://github.com/nipoks/ris1.git
28+
cd ./ris1
29+
```
30+
2. Установить зависимости для всех сервисов:
31+
32+
```
33+
cd ./manager
34+
npm install
35+
cd ..
36+
cd ./worker
37+
npm install
38+
```
39+
3. Запустить проект из корневой папки с помощью Docker Compose:
40+
41+
```
42+
docker-compose up --build
43+
```
44+
45+
# Пример использования
46+
Сайт для генерации хэша по слову: https://www.md5hashgenerator.com/
47+
### 1. Отправка запроса начала:
48+
```
49+
curl -X POST http://localhost:3000/api/hash/crack -H "Content-Type: application/json" -d '{"hash":"3cbdc7e3769a8b04d992a8f93f58c7b9", "maxLength": 5}'
50+
```
51+
Параметры запроса (JSON):
52+
53+
* hash (строка) - искомый MD5 хэш.
54+
* maxLength (целое число) - максимальная длина строки.
55+
56+
Ответ: d13es (~16 сек)
57+
58+
### 2. Получение статуса задачи
59+
```
60+
curl -X GET http://localhost:3000/api/hash/status?requestId=
61+
```
62+
requestId - id который вернул менеджер после создания задачи
63+
* PART_ANSWER_IS_READY - случилась ошибка у воркера, но кто-то нашел вариант ответа
64+
* IN_PROGRESS - кто-то еще работает
65+
* READY - все воркеры отработали успешно
66+
* ERROR
67+
68+
Пример ответа:
69+
```
70+
{ "status" : "IN_PROGRESS",
71+
"data" : {
72+
"answer" : null,
73+
"progress" : "22%"
74+
}
75+
}
76+
```

docker-compose.yml

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
version: '3.8'
2+
3+
services:
4+
manager:
5+
container_name: manager
6+
build: ./manager
7+
ports:
8+
- "3000:3000"
9+
networks:
10+
- crackhash_net
11+
depends_on:
12+
- worker1
13+
- worker2
14+
- worker3
15+
16+
worker1:
17+
build: ./worker
18+
environment:
19+
- WORKER_NAME=worker1
20+
networks:
21+
- crackhash_net
22+
23+
worker2:
24+
build: ./worker
25+
environment:
26+
- WORKER_NAME=worker2
27+
networks:
28+
- crackhash_net
29+
30+
worker3:
31+
build: ./worker
32+
environment:
33+
- WORKER_NAME=worker3
34+
networks:
35+
- crackhash_net
36+
37+
networks:
38+
crackhash_net:

img/plan.jpg

2.97 MB
Loading

manager/Dockerfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM node:18
2+
3+
WORKDIR /app
4+
5+
COPY package.json package-lock.json ./
6+
RUN npm install
7+
8+
COPY . .
9+
10+
EXPOSE 3000
11+
CMD ["node", "src/index.js"]

manager/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.PHONY: env
2+
env:
3+
@cp ./env/env.example .env

manager/env/env.example

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Пример синтаксиса: WORKERS=http://worker1:4000,http://worker2:4000,http://worker3:4000
2+
# Порт указывается в index.js + в dockerfile
3+
# Домен совпадает с именем контейнера в docker-compose.yml
4+
WORKERS=

manager/index.js

Whitespace-only changes.

manager/package-lock.json

+28-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

manager/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"name": "manager",
33
"version": "1.0.0",
4-
"main": "index.js",
4+
"type": "module",
5+
"main": "src/index.js",
56
"scripts": {
67
"test": "echo \"Error: no test specified\" && exit 1"
78
},
@@ -11,6 +12,8 @@
1112
"description": "",
1213
"dependencies": {
1314
"axios": "^1.8.1",
14-
"express": "^4.21.2"
15+
"dotenv": "^16.4.7",
16+
"express": "^4.21.2",
17+
"uuid": "^11.1.0"
1518
}
1619
}

manager/src/controller.js

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import axios from 'axios';
2+
import {v4 as uuidv4} from "uuid";
3+
import dotenv from "dotenv";
4+
5+
6+
dotenv.config();
7+
8+
const WORKERS = process.env.WORKERS ? process.env.WORKERS.split(',') : [];
9+
let requests = {};
10+
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
11+
12+
export const getTaskStatus = async (req, res) => {
13+
const {requestId} = req.query;
14+
if (!requestId || !requests[requestId + 1]) {
15+
return res.status(400).json({error: "Invalid requestId"});
16+
}
17+
let progress = 0
18+
for (let i = 0; i < WORKERS.length; i++) {
19+
await axios.get(`${WORKERS[i]}/internal/api/worker/progress/${requestId}`)
20+
.then(response => {
21+
progress += parseInt(response.data.progress)
22+
})
23+
.catch(error => {
24+
console.log(`Error: worker${WORKERS[i]} - ${error.response.data.error}`);
25+
})
26+
}
27+
28+
progress = Math.floor(progress / WORKERS.length);
29+
console.log(progress);
30+
31+
let countReady = 0
32+
let countError = 0
33+
let answer = []
34+
35+
for (let i = 1; i <= WORKERS.length; i++) {
36+
const status = requests[requestId + i].status;
37+
switch (status) {
38+
case 'IN_PROGRESS': // хоть 1 InProgress - в ответ IP
39+
return res.json({
40+
status: 'IN_PROGRESS',
41+
data: {
42+
answer: null,
43+
progress: `${progress}%`
44+
}
45+
});
46+
case 'READY':
47+
countReady += 1
48+
const answerI = requests[requestId + i].found
49+
if (answerI !== null) {
50+
answer.push(answerI)
51+
}
52+
break;
53+
case 'ERROR':
54+
countError += 1
55+
break;
56+
default:
57+
break;
58+
}
59+
}
60+
61+
if (countReady === WORKERS.length) {
62+
return res.json({
63+
status: 'READY',
64+
data: {
65+
answer: answer,
66+
progress: `100%`
67+
}
68+
});
69+
}
70+
71+
if (countError > 0) {
72+
if (answer.length > 0) { // если есть error но хоть 1 ответ не пустой - ответ PART_ANSWER_IS_READY 'abcd'
73+
return res.json({
74+
status: 'PART_ANSWER_IS_READY',
75+
data: {
76+
answer: answer,
77+
progress: `${progress}%`
78+
}
79+
});
80+
}
81+
82+
return res.json({
83+
status: 'ERROR',
84+
data: null
85+
});
86+
}
87+
88+
return res.json({
89+
status: 'I_D\'NOT_NO',
90+
data: null
91+
});
92+
}
93+
94+
export const postTaskToWorkers = async (req, res) => {
95+
const { hash, maxLength } = req.body;
96+
if (!hash || !maxLength) {
97+
return res.status(400).json({ error: "Missing parameters" });
98+
}
99+
100+
const requestId = uuidv4();
101+
102+
console.log(`Новый запрос: ${requestId}`);
103+
104+
for (let index = 0; index < WORKERS.length; index++) {
105+
requests[requestId + (index + 1)] = {
106+
status: 'PENDING',
107+
found: null
108+
};
109+
axios.post(`${WORKERS[index]}/internal/api/worker/hash/crack/task`, {
110+
hash,
111+
maxLength,
112+
alphabet,
113+
partNumber: index + 1,
114+
partCount: WORKERS.length,
115+
requestId: requestId
116+
}).then(response => {
117+
requests[requestId + (index + 1)].status = 'IN_PROGRESS';
118+
requests[requestId + (index + 1)].found = [''];
119+
})
120+
.catch(error => {
121+
console.error(`Ошибка при отправки задачи воркеру ${index + 1}:`, error.response?.data || error.message);
122+
});
123+
}
124+
125+
res.json({ requestId });
126+
}
127+
128+
export const patchTaskFromWorkers = async (req, res) => {
129+
const { partNumber, found, requestId, status } = req.body;
130+
131+
if (partNumber === undefined || found === undefined) {
132+
return res.status(400).json({ error: "Invalid result data" });
133+
}
134+
135+
console.log(`Получен результат от Worker ${partNumber}: ${found ? found : "ничего не найдено"}`);
136+
requests[requestId + partNumber].found = found;
137+
requests[requestId + partNumber].status = status;
138+
res.status(200).json({ message: "Результат принят" });
139+
}

manager/src/index.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import express from 'express';
2+
import dotenv from 'dotenv';
3+
import { AppRoutes } from "./routes.js";
4+
5+
dotenv.config();
6+
7+
const app = express();
8+
app.use(express.json());
9+
app.use(AppRoutes);
10+
11+
app.listen(3000, () => {
12+
console.log("Запущен на порту 3000");
13+
});

manager/src/routes.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import express from 'express';
2+
import * as managerController from './controller.js';
3+
4+
const appRouter = express.Router()
5+
6+
appRouter.get('/api/hash/status', managerController.getTaskStatus)
7+
appRouter.post('/api/hash/crack', managerController.postTaskToWorkers)
8+
appRouter.patch('/internal/api/manager/hash/crack/request', managerController.patchTaskFromWorkers)
9+
10+
export const AppRoutes = appRouter;

worker/Dockerfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM node:18
2+
3+
WORKDIR /app
4+
5+
COPY package.json package-lock.json ./
6+
RUN npm install
7+
8+
COPY . .
9+
10+
EXPOSE 4000
11+
CMD ["node", "src/index.js"]

worker/Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.PHONY: env
2+
env:
3+
@cp ./env/env.example .env

0 commit comments

Comments
 (0)