修复偏移问题,修复代理问题
This commit is contained in:
@@ -7,6 +7,8 @@ import requests
|
||||
from urllib.parse import urlparse
|
||||
from app.models import SystemConfig, db
|
||||
from app.utils.octoprint_client import OctoPrintClient
|
||||
from app.models import PrintFile
|
||||
import os
|
||||
|
||||
printer_bp = Blueprint('printer', __name__, url_prefix='/printer')
|
||||
|
||||
@@ -35,20 +37,69 @@ def status():
|
||||
|
||||
return render_template('printer/status.html', status=status_data, job=job_data, error=error)
|
||||
|
||||
def get_gcode_dir():
|
||||
conf = SystemConfig.query.filter_by(key='gcode_upload_folder').first()
|
||||
if conf and conf.value and os.path.exists(conf.value):
|
||||
return conf.value
|
||||
return current_app.config['UPLOAD_FOLDER']
|
||||
|
||||
@printer_bp.route('/prepare')
|
||||
@login_required
|
||||
def prepare():
|
||||
client = get_octo_client()
|
||||
from app.models import PrintFile
|
||||
import os
|
||||
|
||||
# Query only the sliced GCode files belonging to the current user
|
||||
user_files = PrintFile.query.filter_by(user_id=current_user.id, status='sliced').order_by(PrintFile.created_at.desc()).all()
|
||||
|
||||
files = []
|
||||
error = None
|
||||
gcode_dir = get_gcode_dir()
|
||||
|
||||
client = get_octo_client()
|
||||
octo_files_dict = {}
|
||||
if client:
|
||||
try:
|
||||
res = client.get_files()
|
||||
files = res.get('files', [])
|
||||
octo_resp = client.get_files()
|
||||
for item in octo_resp.get('files', []):
|
||||
octo_files_dict[item.get('name')] = item
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
else:
|
||||
pass
|
||||
|
||||
for f in user_files:
|
||||
gcode_filename = f.filename.rsplit('.', 1)[0] + '.gcode'
|
||||
gcode_path = os.path.join(gcode_dir, gcode_filename)
|
||||
|
||||
size = 0
|
||||
if os.path.exists(gcode_path):
|
||||
size = os.path.getsize(gcode_path)
|
||||
|
||||
# Upload to OctoPrint if not found but exists locally
|
||||
if client and gcode_filename not in octo_files_dict and size > 0:
|
||||
try:
|
||||
resp = client.upload_file('local', gcode_path, gcode_filename)
|
||||
uploaded_loc = resp.get('files', {}).get('local', {})
|
||||
if gcode_filename in uploaded_loc:
|
||||
octo_files_dict[gcode_filename] = uploaded_loc[gcode_filename]
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
octo_info = octo_files_dict.get(gcode_filename, {})
|
||||
analysis = octo_info.get('gcodeAnalysis', None)
|
||||
|
||||
files.append({
|
||||
'id': f.id,
|
||||
'name': f.original_filename.rsplit('.', 1)[0] + '.gcode',
|
||||
'type': 'machinecode',
|
||||
'size': size,
|
||||
'origin': 'local',
|
||||
'path': gcode_filename,
|
||||
'gcodeAnalysis': analysis
|
||||
})
|
||||
|
||||
error = None
|
||||
if not get_octo_client():
|
||||
error = "OctoPrint is not configured."
|
||||
|
||||
return render_template('printer/prepare.html', files=files, error=error)
|
||||
|
||||
@printer_bp.route('/api/print_file', methods=['POST'])
|
||||
@@ -98,6 +149,54 @@ def api_command():
|
||||
return jsonify({"success": False, "error": str(e)})
|
||||
return jsonify({"success": False, "error": "Invalid client or command"})
|
||||
|
||||
@printer_bp.route('/api/upload_gcode', methods=['POST'])
|
||||
@login_required
|
||||
def upload_gcode():
|
||||
from app.models import PrintFile
|
||||
import os
|
||||
import uuid
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
if 'file' not in request.files:
|
||||
return jsonify({"success": False, "error": "No file part"}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({"success": False, "error": "No selected file"}), 400
|
||||
|
||||
if not file.filename.lower().endswith(('.gcode', '.gco', '.g')):
|
||||
return jsonify({"success": False, "error": "Only standard .gcode files are supported."}), 400
|
||||
|
||||
sec_name = secure_filename(file.filename)
|
||||
random_prefix = uuid.uuid4().hex[:8]
|
||||
|
||||
# We save pseudo-STL filename so that our conventional parser works (replaces .ext with .gcode)
|
||||
# i.e., "some_file.gcode" -> pseudo .stl tracker
|
||||
pseudo_stl_filename = f"{random_prefix}_{sec_name.rsplit('.', 1)[0]}.stl"
|
||||
gcode_filename = f"{random_prefix}_{sec_name.rsplit('.', 1)[0]}.gcode"
|
||||
|
||||
gcode_path = os.path.join(get_gcode_dir(), gcode_filename)
|
||||
file.save(gcode_path)
|
||||
|
||||
print_file = PrintFile(
|
||||
user_id=current_user.id,
|
||||
original_filename=file.filename, # keep original GCode name
|
||||
filename=pseudo_stl_filename,
|
||||
file_type='stl',
|
||||
status='sliced'
|
||||
)
|
||||
db.session.add(print_file)
|
||||
db.session.commit()
|
||||
|
||||
client = get_octo_client()
|
||||
if client:
|
||||
try:
|
||||
client.upload_file('local', gcode_path, gcode_filename)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return jsonify({"success": True})
|
||||
|
||||
@printer_bp.route('/octo_config', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def octo_config():
|
||||
@@ -141,9 +240,9 @@ def octo_embed():
|
||||
embed_url = url_for('printer.octo_proxy') if url and url.value else None
|
||||
return render_template('printer/octo_embed.html', embed_url=embed_url)
|
||||
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, websocket=True)
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, websocket=True, strict_slashes=False)
|
||||
@printer_bp.route('/proxy/<path:path>', websocket=True)
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], strict_slashes=False)
|
||||
@printer_bp.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
||||
@login_required
|
||||
def octo_proxy(path):
|
||||
@@ -159,7 +258,7 @@ def octo_proxy(path):
|
||||
# print("----- REQUEST HEADERS -----")
|
||||
# for k, v in request.headers:
|
||||
# print(f"{k}: {v}")
|
||||
# print("----- END HEADERS -----")
|
||||
# print("----- END REQUEST HEADERS -----")
|
||||
|
||||
# --- WebSocket Proxy Logic ---
|
||||
if request.headers.get('Upgrade', '').lower() == 'websocket':
|
||||
@@ -278,17 +377,26 @@ def octo_proxy(path):
|
||||
|
||||
# Build headers for reverse proxy based on nginx config reference
|
||||
parsed_base = urlparse(base_url)
|
||||
headers = {k: v for k, v in request.headers if k.lower() not in ['host', 'content-length']}
|
||||
headers = {k: v for k, v in request.headers if k.lower() not in ['host', 'content-length', 'origin', 'referer']}
|
||||
|
||||
# NGINX equivalent proxy headers
|
||||
# print(f"Proxying to: {target_url}")
|
||||
# Spoof Host, Origin, and Referer to match the backend URL completely
|
||||
# This prevents Tornado's strict Origin vs Host CSRF/CORS validation from failing
|
||||
# headers['Host'] = parsed_base.netloc
|
||||
headers['Host'] = request.host
|
||||
|
||||
if 'Origin' in request.headers:
|
||||
headers['Origin'] = base_url
|
||||
if 'Referer' in request.headers:
|
||||
headers['Referer'] = f"{base_url}/{path}"
|
||||
|
||||
headers['X-Real-IP'] = request.remote_addr
|
||||
headers['X-Real-Port'] = str(request.environ.get('REMOTE_PORT', ''))
|
||||
|
||||
forwarded_for = request.headers.get('X-Forwarded-For', '')
|
||||
headers['X-Forwarded-For'] = f"{forwarded_for}, {request.remote_addr}" if forwarded_for else request.remote_addr
|
||||
|
||||
headers['X-Forwarded-Protocol'] = request.scheme
|
||||
headers['X-Forwarded-Proto'] = request.scheme
|
||||
headers['X-Script-Name'] = "/printer/proxy"
|
||||
headers['X-Forwarded-Host'] = request.host
|
||||
headers['X-Forwarded-Port'] = str(request.environ.get('SERVER_PORT', '80'))
|
||||
@@ -299,6 +407,27 @@ def octo_proxy(path):
|
||||
if request.headers.get('Connection'):
|
||||
headers['Connection'] = request.headers.get('Connection')
|
||||
|
||||
# OctoPrint requires an X-CSRF-Token header matching the csrf_token_* cookie for POST/PUT/DELETE
|
||||
if request.method not in ['GET', 'HEAD', 'OPTIONS'] and 'X-CSRF-Token' not in request.headers:
|
||||
for cookie_name, cookie_value in request.cookies.items():
|
||||
if cookie_name.startswith('csrf_token_'):
|
||||
headers['X-CSRF-Token'] = cookie_value
|
||||
break
|
||||
|
||||
# if path == 'api/login':
|
||||
# print("----- SEND HEADERS -----")
|
||||
# for k, v in headers.items():
|
||||
# if k in request.headers.keys():
|
||||
# print(f"{k} :(from request): {request.headers[k]} (to send): {v}")
|
||||
# else:
|
||||
# print(f"{k} :(from request): None (to send): {v}")
|
||||
|
||||
# for k,v in request.headers.items():
|
||||
# if k not in headers.keys():
|
||||
# print(f"{k} :(from request): {request.headers[k]} (to send): None")
|
||||
|
||||
# print("----- END SEND HEADERS -----")
|
||||
|
||||
try:
|
||||
# proxy_connect_timeout 60s, proxy_read_timeout 600s
|
||||
resp = requests.request(
|
||||
@@ -316,7 +445,15 @@ def octo_proxy(path):
|
||||
|
||||
# Strip headers that might break the iframe or framing
|
||||
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection', 'x-frame-options', 'content-security-policy']
|
||||
response_headers = [(name, value) for (name, value) in resp.headers.items() if name.lower() not in excluded_headers]
|
||||
|
||||
# We must use raw headers to prevent requests from joining multiple Set-Cookie headers with a comma
|
||||
# Joining Set-Cookie with a comma breaks standard cookie parsing in the browser due to commas in dates
|
||||
response_headers = [(name, value) for name, value in resp.raw.headers.items()
|
||||
if name.lower() not in excluded_headers and name.lower() != 'set-cookie']
|
||||
|
||||
for cookie in resp.raw.headers.get_all('set-cookie', []):
|
||||
# ensure we preserve the proxy path override
|
||||
response_headers.append(('Set-Cookie', cookie))
|
||||
|
||||
def generate():
|
||||
for chunk in resp.iter_content(chunk_size=8192):
|
||||
|
||||
Reference in New Issue
Block a user